From 1e22f473049f094f821249dba26527f53764fe7a Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Mon, 21 Oct 2019 22:12:22 -0700 Subject: [PATCH] Merge from vscode c58aaab8a1cc22a7139b761166a0d4f37d41e998 (#7880) * Merge from vscode c58aaab8a1cc22a7139b761166a0d4f37d41e998 * fix pipelines * fix strict-null-checks * add missing files --- .github/pull_request_template.md | 9 + .vscode/launch.json | 27 + .vscode/settings.json | 1 + .vscode/tasks.json | 23 +- .yarnrc | 2 +- azure-pipelines-linux-mac.yml | 2 +- azure-pipelines-windows.yml | 2 +- .../darwin/continuous-build-darwin.yml | 2 +- .../linux/continuous-build-linux.yml | 2 +- .../win32/continuous-build-win32.yml | 2 +- .../win32/product-build-win32.yml | 2 +- build/gulpfile.editor.js | 3 - build/gulpfile.hygiene.js | 2 +- build/gulpfile.vscode.js | 69 +- build/gulpfile.vscode.linux.js | 2 +- build/lib/electron.js | 99 +- build/lib/electron.ts | 100 + build/lib/util.js | 38 +- build/lib/util.ts | 42 +- build/monaco/api.js | 2 +- build/monaco/api.ts | 2 +- build/package.json | 4 +- build/win32/Cargo.lock | 2 +- build/win32/inno_updater.exe | Bin 396800 -> 403456 bytes build/yarn.lock | 16 +- cgmanifest.json | 34 +- extensions/configuration-editing/package.json | 11 +- .../schemas/attachContainer.schema.json | 36 + .../configuration-editing/src/extension.ts | 3 +- extensions/git/package.json | 24 + extensions/git/package.nls.json | 3 +- extensions/git/src/askpass-main.ts | 4 +- extensions/git/src/autofetch.ts | 2 +- extensions/git/src/commands.ts | 127 +- extensions/git/src/git.ts | 72 +- extensions/git/src/repository.ts | 61 +- extensions/git/src/util.ts | 2 +- extensions/image-preview/README.md | 14 + extensions/image-preview/media/main.css | 35 +- extensions/image-preview/media/main.js | 78 +- extensions/image-preview/package.json | 3 +- extensions/image-preview/package.nls.json | 2 +- extensions/image-preview/src/preview.ts | 120 +- .../image-preview/src/sizeStatusBarEntry.ts | 22 +- .../image-preview/src/zoomStatusBarEntry.ts | 21 +- .../json-language-features/server/README.md | 14 +- .../server/package.json | 2 +- .../server/src/jsonServerMain.ts | 7 +- extensions/json/language-configuration.json | 3 +- extensions/json/package.json | 3 +- .../syntaxes/markdown.tmLanguage.json | 38 +- .../media/highlight.css | 19 +- .../markdown-language-features/media/index.js | 1010 +--------- .../markdown-language-features/media/pre.js | 282 +-- .../markdown-language-features/package.json | 676 +++---- .../preview-src/index.ts | 54 +- .../src/extension.ts | 2 +- .../src/features/preview.ts | 52 +- .../src/markdownEngine.ts | 89 +- .../src/util/links.ts | 26 +- .../markdown-language-features/yarn.lock | 26 +- .../theme-abyss/themes/abyss-color-theme.json | 9 - .../themes/kimbie-dark-color-theme.json | 4 - .../themes/dimmed-monokai-color-theme.json | 16 - .../themes/monokai-color-theme.json | 11 +- .../themes/quietlight-color-theme.json | 44 - .../theme-red/themes/Red-color-theme.json | 26 - .../themes/solarized-dark-color-theme.json | 7 - .../themes/solarized-light-color-theme.json | 2 - package.json | 21 +- remote/.yarnrc | 2 +- remote/package.json | 9 +- remote/web/package.json | 7 +- remote/web/yarn.lock | 92 +- remote/yarn.lock | 156 +- resources/linux/bin/code.sh | 2 +- resources/linux/code-url-handler.desktop | 2 +- resources/linux/code.desktop | 4 +- resources/win32/inno-big-125.bmp | Bin resources/win32/inno-big-150.bmp | Bin resources/win32/inno-big-175.bmp | Bin resources/win32/inno-big-200.bmp | Bin resources/win32/inno-big-225.bmp | Bin resources/win32/inno-small-100.bmp | Bin resources/win32/inno-small-125.bmp | Bin resources/win32/inno-small-150.bmp | Bin resources/win32/inno-small-175.bmp | Bin resources/win32/inno-small-200.bmp | Bin resources/win32/inno-small-225.bmp | Bin resources/win32/inno-small-250.bmp | Bin scripts/code-cli.bat | 2 +- scripts/code-cli.sh | 5 +- scripts/code-web.js | 225 +++ scripts/code.bat | 2 +- scripts/code.sh | 4 +- scripts/node-electron.sh | 2 +- scripts/test-integration.bat | 16 +- scripts/test-integration.sh | 15 +- scripts/test.sh | 4 +- src/bootstrap-window.js | 9 +- src/bootstrap.js | 18 +- src/main.js | 361 +++- .../browser/ui/dropdownList/dropdownList.ts | 12 +- src/sql/base/browser/ui/listBox/listBox.ts | 6 +- .../base/browser/ui/selectBox/selectBox.ts | 4 +- .../editableDropdown/browser/dropdown.ts | 4 +- .../formContainer.component.ts | 4 - .../modelComponents/webview.component.ts | 2 - .../contents/webviewContent.component.ts | 4 +- .../webview/webviewWidget.component.ts | 4 +- .../parts/query/browser/queryResultsEditor.ts | 9 +- .../electron-browser/insightsUtils.test.ts | 1 + src/typings/electron.d.ts | 1761 ++++++++++++----- src/typings/xterm.d.ts | 51 +- src/vs/base/browser/contextmenu.ts | 1 + src/vs/base/browser/fastDomNode.ts | 10 + src/vs/base/browser/mouseEvent.ts | 63 +- src/vs/base/browser/touch.ts | 2 +- .../base/browser/ui/actionbar/actionbar.css | 6 - src/vs/base/browser/ui/actionbar/actionbar.ts | 113 +- src/vs/base/browser/ui/button/button.ts | 10 +- src/vs/base/browser/ui/checkbox/checkbox.ts | 8 +- .../codicon/codicon-animations.css | 2 +- .../ui/codiconLabel/codicon/codicon.css | 515 ++--- .../ui/codiconLabel/codicon/codicon.ttf | Bin 52728 -> 52620 bytes .../browser/ui/codiconLabel/codiconLabel.ts | 2 +- .../browser/ui/contextview/contextview.ts | 2 +- .../base/browser/ui/countBadge/countBadge.ts | 10 +- src/vs/base/browser/ui/dialog/dialog.ts | 16 +- src/vs/base/browser/ui/dropdown/dropdown.ts | 16 +- .../base/browser/ui/findinput/replaceInput.ts | 175 +- src/vs/base/browser/ui/grid/grid.ts | 1 - .../ui/highlightedlabel/highlightedLabel.ts | 18 +- src/vs/base/browser/ui/iconLabel/iconLabel.ts | 20 +- .../base/browser/ui/iconLabel/iconlabel.css | 4 +- src/vs/base/browser/ui/inputbox/inputBox.ts | 24 +- .../ui/keybindingLabel/keybindingLabel.ts | 10 +- src/vs/base/browser/ui/list/list.ts | 16 + src/vs/base/browser/ui/list/listPaging.ts | 8 + src/vs/base/browser/ui/list/listView.ts | 30 +- src/vs/base/browser/ui/list/listWidget.ts | 25 +- src/vs/base/browser/ui/menu/menu.css | 3 +- src/vs/base/browser/ui/menu/menu.ts | 183 +- src/vs/base/browser/ui/menu/menubar.ts | 55 +- .../ui/octiconLabel/octiconLabel.mock.ts | 24 - .../browser/ui/octiconLabel/octiconLabel.ts | 33 - .../octicons/octicons-animations.css | 14 - .../ui/octiconLabel/octicons/octicons.css | 251 --- .../ui/octiconLabel/octicons/octicons.ttf | Bin 35428 -> 0 bytes .../browser/ui/progressbar/progressbar.ts | 14 +- src/vs/base/browser/ui/selectBox/selectBox.ts | 2 +- .../browser/ui/selectBox/selectBoxCustom.ts | 26 +- .../browser/ui/selectBox/selectBoxNative.ts | 6 +- .../base/browser/ui/splitview/panelview.css | 1 + src/vs/base/browser/ui/splitview/panelview.ts | 20 +- src/vs/base/browser/ui/toolbar/toolbar.ts | 4 +- src/vs/base/browser/ui/tree/abstractTree.ts | 52 +- src/vs/base/browser/ui/tree/asyncDataTree.ts | 8 +- src/vs/base/browser/ui/tree/objectTree.ts | 9 +- src/vs/base/common/actions.ts | 18 - src/vs/base/common/cancellation.ts | 5 +- src/vs/base/common/codicon.ts | 135 ++ src/vs/base/common/color.ts | 9 - src/vs/base/common/diff/diff.ts | 262 ++- src/vs/base/common/hash.ts | 2 +- src/vs/base/common/htmlContent.ts | 30 +- src/vs/base/common/iterator.ts | 2 +- src/vs/base/common/labels.ts | 18 +- src/vs/base/common/lazy.ts | 65 + src/vs/base/common/lifecycle.ts | 2 +- src/vs/base/common/map.ts | 4 +- src/vs/base/common/network.ts | 36 +- src/vs/base/common/octicon.ts | 126 -- src/vs/base/common/path.ts | 4 +- src/vs/base/common/platform.ts | 28 + src/vs/base/common/resourceTree.ts | 180 +- src/vs/base/common/search.ts | 17 +- src/vs/base/common/strings.ts | 13 +- src/vs/base/common/types.ts | 33 + .../common/core => base/common}/uint.ts | 26 - src/vs/base/common/uri.ts | 48 +- src/vs/base/node/encoding.ts | 6 +- src/vs/base/node/languagePacks.d.ts | 1 + src/vs/base/node/macAddress.ts | 16 +- src/vs/base/node/pfs.ts | 16 +- src/vs/base/node/processes.ts | 78 +- src/vs/base/node/zip.ts | 2 +- .../electron-browser/contextmenu.ts | 4 +- .../contextmenu/electron-main/contextmenu.ts | 2 +- src/vs/base/parts/ipc/common/ipc.ts | 1 - .../ipc/electron-main/ipc.electron-main.ts | 2 +- .../parts/quickopen/browser/quickOpenModel.ts | 14 +- .../quickopen/browser/quickOpenWidget.ts | 27 +- src/vs/base/parts/storage/node/storage.ts | 20 +- src/vs/base/parts/tree/browser/treeView.ts | 17 +- src/vs/base/test/common/cancellation.test.ts | 14 + .../{octicon.test.ts => codicon.test.ts} | 28 +- src/vs/base/test/common/color.test.ts | 3 +- src/vs/base/test/common/diff/diff.test.ts | 77 +- src/vs/base/test/common/lazy.test.ts | 64 + src/vs/base/test/common/resourceTree.test.ts | 56 +- src/vs/base/test/common/types.test.ts | 18 + src/vs/base/test/common/uri.test.ts | 31 + src/vs/base/test/node/pfs/pfs.test.ts | 26 + .../code/browser/workbench/workbench-dev.html | 20 +- src/vs/code/browser/workbench/workbench.html | 21 +- src/vs/code/browser/workbench/workbench.ts | 120 +- .../electron-browser/issue/issueReporter.js | 2 +- .../issue/issueReporterMain.ts | 9 +- .../issue/media/issueReporter.css | 2 +- .../processExplorer/media/collapsed.svg | 0 .../processExplorer/media/expanded.svg | 0 .../processExplorer/processExplorerMain.ts | 12 +- .../contrib/storageDataCleaner.ts | 2 +- src/vs/code/electron-main/app.ts | 31 +- src/vs/code/electron-main/main.ts | 37 +- src/vs/code/electron-main/window.ts | 457 +++-- src/vs/code/node/cli.ts | 6 +- src/vs/code/node/paths.ts | 12 +- .../electron-main/windowsStateStorage.test.ts | 4 +- .../editor/browser/config/charWidthReader.ts | 3 + src/vs/editor/browser/config/configuration.ts | 20 +- .../editor/browser/controller/mouseHandler.ts | 2 +- .../editor/browser/controller/mouseTarget.ts | 2 +- .../browser/controller/textAreaHandler.ts | 99 +- .../browser/controller/textAreaInput.ts | 117 +- src/vs/editor/browser/editorBrowser.ts | 21 + src/vs/editor/browser/editorExtensions.ts | 71 +- .../services/abstractCodeEditorService.ts | 8 +- .../browser/services/codeEditorService.ts | 9 +- .../browser/services/codeEditorServiceImpl.ts | 4 +- .../editor/browser/services/openerService.ts | 73 +- src/vs/editor/browser/view/viewController.ts | 4 +- .../contentWidgets/contentWidgets.ts | 4 +- .../browser/viewParts/lines/rangeUtil.ts | 2 +- .../browser/viewParts/lines/viewLine.ts | 7 +- .../browser/viewParts/lines/viewLines.ts | 3 +- .../browser/viewParts/minimap/minimap.ts | 81 +- .../viewParts/minimap/minimapCharRenderer.ts | 121 ++ .../minimap/minimapCharRendererFactory.ts | 161 ++ .../viewParts/minimap/minimapCharSheet.ts | 39 + .../viewParts/selections/selections.ts | 37 +- .../viewParts/viewCursors/viewCursors.ts | 2 +- .../editor/browser/widget/codeEditorWidget.ts | 24 +- .../editor/browser/widget/diffEditorWidget.ts | 101 +- src/vs/editor/browser/widget/diffReview.ts | 6 +- .../widget/embeddedCodeEditorWidget.ts | 6 +- src/vs/editor/browser/widget/media/editor.css | 6 - .../editor/common/commands/replaceCommand.ts | 21 + .../common/config/commonEditorConfig.ts | 16 + src/vs/editor/common/config/editorOptions.ts | 385 ++-- src/vs/editor/common/config/fontInfo.ts | 18 +- src/vs/editor/common/controller/cursor.ts | 2 +- .../editor/common/controller/cursorCommon.ts | 23 + .../common/controller/cursorMoveCommands.ts | 11 +- .../common/controller/cursorTypeOperations.ts | 19 +- .../editor/common/core/characterClassifier.ts | 2 +- src/vs/editor/common/core/rgba.ts | 2 +- src/vs/editor/common/core/selection.ts | 7 - src/vs/editor/common/diff/diffComputer.ts | 247 +-- src/vs/editor/common/editorCommon.ts | 21 +- src/vs/editor/common/model.ts | 18 +- .../pieceTreeTextBuffer.ts | 31 + src/vs/editor/common/model/textModel.ts | 452 +++-- src/vs/editor/common/model/textModelSearch.ts | 2 +- src/vs/editor/common/model/textModelTokens.ts | 28 +- src/vs/editor/common/modes.ts | 113 +- .../common/modes/languageConfiguration.ts | 2 +- src/vs/editor/common/modes/linkComputer.ts | 27 +- src/vs/editor/common/modes/modesRegistry.ts | 14 +- .../modes/supports/electricCharacter.ts | 11 +- .../common/modes/supports/richEditBrackets.ts | 280 ++- .../common/modes/textToHtmlTokenizer.ts | 6 +- .../common/services/editorSimpleWorker.ts | 59 +- .../common/services/editorWorkerService.ts | 3 +- .../services/editorWorkerServiceImpl.ts | 8 +- .../common/services/modelServiceImpl.ts | 4 + .../common/standalone/standaloneEnums.ts | 6 +- .../editor/common/view/minimapCharRenderer.ts | 349 ---- .../common/view/runtimeMinimapCharRenderer.ts | 985 --------- .../common/viewLayout/lineDecorations.ts | 2 +- .../characterHardWrappingLineMapper.ts | 2 +- .../viewModel/minimapTokensColorTracker.ts | 63 + .../common/viewModel/prefixSumComputer.ts | 2 +- .../editor/common/viewModel/viewModelImpl.ts | 44 +- .../bracketMatching/bracketMatching.ts | 100 +- .../test/bracketMatching.test.ts | 74 +- .../contrib/codeAction/codeActionCommands.ts | 35 +- .../codeAction/codeActionContributions.ts | 2 +- .../editor/contrib/codeAction/codeActionUi.ts | 39 +- .../contrib/codeAction/codeActionWidget.ts | 3 +- .../contrib/codeAction/lightBulbWidget.css | 2 + .../contrib/codeAction/lightBulbWidget.ts | 50 +- .../contrib/codelens/codelensController.ts | 8 +- .../editor/contrib/codelens/codelensWidget.ts | 4 +- .../contrib/colorPicker/colorDetector.ts | 10 +- .../contrib/colorPicker/colorPickerWidget.ts | 6 +- .../contrib/comment/lineCommentCommand.ts | 3 +- .../editor/contrib/contextmenu/contextmenu.ts | 22 +- .../editor/contrib/cursorUndo/cursorUndo.ts | 98 +- src/vs/editor/contrib/dnd/dnd.ts | 10 +- .../documentSymbols/media/outlineTree.css | 2 +- .../contrib/documentSymbols/outlineTree.ts | 37 +- src/vs/editor/contrib/find/findController.ts | 8 +- src/vs/editor/contrib/find/findModel.ts | 2 +- .../contrib/find/test/findController.test.ts | 36 +- .../contrib/find/test/replacePattern.test.ts | 24 + src/vs/editor/contrib/folding/folding.ts | 14 +- .../contrib/folding/foldingDecorations.ts | 6 +- src/vs/editor/contrib/format/formatActions.ts | 16 +- .../goToDefinition/goToDefinitionCommands.ts | 5 +- .../goToDefinition/goToDefinitionMouse.ts | 12 +- .../goToDefinitionResultsNavigation.ts | 3 +- src/vs/editor/contrib/gotoError/gotoError.ts | 32 +- .../contrib/gotoError/gotoErrorWidget.ts | 2 +- src/vs/editor/contrib/hover/hover.ts | 43 +- .../editor/contrib/hover/modesContentHover.ts | 5 +- .../contrib/inPlaceReplace/inPlaceReplace.ts | 8 +- .../editor/contrib/indentation/indentation.ts | 8 +- .../linesOperations/linesOperations.ts | 44 +- .../test/copyLinesCommand.test.ts | 61 + src/vs/editor/contrib/links/links.ts | 19 +- .../contrib/markdown/markdownRenderer.ts | 2 +- .../contrib/message/messageController.ts | 12 +- .../editor/contrib/multicursor/multicursor.ts | 18 +- .../multicursor/test/multicursor.test.ts | 24 +- .../contrib/parameterHints/parameterHints.ts | 8 +- .../parameterHints/parameterHintsModel.ts | 68 +- .../parameterHints/parameterHintsWidget.ts | 104 +- .../test/parameterHintsModel.test.ts | 294 +-- .../referenceSearch/media/peekViewWidget.css | 6 +- .../contrib/referenceSearch/peekViewWidget.ts | 4 +- .../referenceSearch/referenceSearch.ts | 8 +- .../referenceSearch/referencesController.ts | 6 +- .../referenceSearch/referencesModel.ts | 3 +- .../referenceSearch/referencesWidget.ts | 29 +- src/vs/editor/contrib/rename/rename.ts | 152 +- .../editor/contrib/rename/renameInputField.ts | 4 +- .../contrib/smartSelect/bracketSelections.ts | 4 +- .../editor/contrib/smartSelect/smartSelect.ts | 10 +- .../smartSelect/test/smartSelect.test.ts | 24 +- .../contrib/snippet/snippetController2.ts | 16 +- .../editor/contrib/snippet/snippetParser.ts | 28 +- .../editor/contrib/snippet/snippetSession.ts | 7 +- .../contrib/snippet/snippetVariables.ts | 58 +- .../test/snippetController2.old.test.ts | 2 +- .../snippet/test/snippetParser.test.ts | 13 + .../snippet/test/snippetVariables.test.ts | 33 +- .../contrib/suggest/suggestAlternatives.ts | 2 +- .../contrib/suggest/suggestController.ts | 128 +- .../editor/contrib/suggest/suggestWidget.ts | 9 +- .../suggest/test/completionModel.test.ts | 3 + .../contrib/suggest/test/suggestModel.test.ts | 10 +- .../wordHighlighter/wordHighlighter.ts | 8 +- .../editor/contrib/zoneWidget/zoneWidget.ts | 72 +- .../accessibilityHelp/accessibilityHelp.ts | 8 +- .../iPadShowKeyboard/iPadShowKeyboard.ts | 8 +- .../browser/inspectTokens/inspectTokens.ts | 8 +- .../browser/quickOpen/editorQuickOpen.ts | 8 +- .../quickOpen/quickOpenEditorWidget.ts | 27 +- .../browser/quickOpen/quickOutline.ts | 4 +- .../standaloneReferenceSearch.ts | 2 +- .../standalone/browser/simpleServices.ts | 16 +- .../browser/standaloneCodeEditor.ts | 4 +- .../browser/standaloneCodeServiceImpl.ts | 12 +- .../standalone/browser/standaloneEditor.ts | 2 + .../test/browser/controller/cursor.test.ts | 77 +- .../test/browser/controller/imeTester.ts | 10 +- .../editor/test/browser/editorTestServices.ts | 6 +- .../services/decorationRenderOptions.test.ts | 8 +- src/vs/editor/test/browser/testCodeEditor.ts | 4 +- .../view/minimapCharRenderer.test.ts | 87 +- .../test/browser/view/minimapFontCreator.ts | 87 +- .../controller/cursorMoveHelper.test.ts | 58 +- src/vs/editor/test/common/core/range.test.ts | 4 +- .../test/common/diff/diffComputer.test.ts | 5 +- .../test/common/mocks/testConfiguration.ts | 3 +- .../test/common/model/textModelSearch.test.ts | 14 + .../common/model/textModelWithTokens.test.ts | 142 +- .../modes/supports/characterPair.test.ts | 10 +- .../modes/supports/richEditBrackets.test.ts | 30 +- .../common/modes/textToHtmlTokenizer.test.ts | 22 +- .../test/common/services/modelService.test.ts | 10 +- .../common/view/minimapCharRendererFactory.ts | 172 -- .../viewLayout/editorLayoutProvider.test.ts | 13 +- .../viewModel/prefixSumComputer.test.ts | 2 +- .../viewModel/splitLinesCollection.test.ts | 2 +- .../common/viewModel/viewModelImpl.test.ts | 2 +- src/vs/monaco.d.ts | 48 +- .../browser/menuEntryActionViewItem.ts | 25 +- src/vs/platform/actions/common/actions.ts | 12 +- .../backup/electron-main/backupMainService.ts | 20 +- src/vs/platform/commands/common/commands.ts | 2 +- .../common/configurationModels.ts | 15 +- .../common/configurationRegistry.ts | 8 +- .../contextkey/browser/contextKeyService.ts | 2 +- .../debug/common/extensionHostDebug.ts | 3 +- .../debug/common/extensionHostDebugIpc.ts | 3 +- .../diagnostics/node/diagnosticsService.ts | 10 +- .../platform/dialogs/electron-main/dialogs.ts | 52 +- src/vs/platform/dialogs/node/dialogs.ts | 13 - .../platform/driver/electron-main/driver.ts | 10 +- src/vs/platform/editor/common/editor.ts | 27 + .../electron-main/electronMainService.ts | 220 +- src/vs/platform/electron/node/electron.ts | 10 +- .../environment/common/environment.ts | 17 +- src/vs/platform/environment/node/argv.ts | 11 +- .../environment/node/environmentService.ts | 11 +- .../platform/extensions/common/extensions.ts | 9 + src/vs/platform/files/common/fileService.ts | 29 +- .../files/node/diskFileSystemProvider.ts | 37 +- .../watcher/unix/chokidarWatcherService.ts | 85 +- .../files/test/node/diskFileService.test.ts | 14 +- .../common/instantiationService.ts | 20 +- .../issue/electron-main/issueMainService.ts | 12 +- .../keybinding/common/keybindingsRegistry.ts | 5 +- .../common/abstractKeybindingService.test.ts | 3 +- .../launch/electron-main/launchMainService.ts | 13 +- .../lifecycle/common/lifecycleService.ts | 2 +- .../electron-main/lifecycleMainService.ts | 19 +- src/vs/platform/list/browser/listService.ts | 22 +- .../platform/menubar/electron-main/menubar.ts | 29 +- .../notification/common/notification.ts | 26 + .../test/common/testNotificationService.ts | 6 +- src/vs/platform/opener/common/opener.ts | 22 +- .../platform/product/common/productService.ts | 2 - src/vs/platform/progress/common/progress.ts | 7 +- .../severityIcon/common/severityIcon.ts | 91 +- src/vs/platform/state/node/stateService.ts | 2 +- .../storage/browser/storageService.ts | 27 +- src/vs/platform/storage/node/storageIpc.ts | 2 +- .../storage/node/storageMainService.ts | 2 +- .../platform/storage/node/storageService.ts | 29 +- .../browser/workbenchCommonProperties.ts | 14 +- .../telemetry/common/telemetryService.ts | 4 +- .../telemetry/common/telemetryUtils.ts | 2 +- .../telemetry/node/commonProperties.ts | 22 +- .../node/workbenchCommonProperties.ts | 5 +- .../test/browser/commonProperties.test.ts | 62 + .../electron-browser/commonProperties.test.ts | 48 - src/vs/platform/theme/common/colorRegistry.ts | 70 +- src/vs/platform/theme/common/styler.ts | 12 +- .../electron-main/updateService.darwin.ts | 6 +- src/vs/platform/url/common/url.ts | 15 +- src/vs/platform/url/common/urlIpc.ts | 9 +- src/vs/platform/url/common/urlService.ts | 6 +- .../url/electron-main/electronUrlListener.ts | 45 +- .../userDataSync/common/extensionsSync.ts | 14 +- .../userDataSync/common/settingsSync.ts | 24 +- .../userDataSync/common/userDataSync.ts | 5 +- .../common/userDataSyncService.ts | 17 +- .../common/userDataSyncStoreService.ts | 12 +- src/vs/platform/windows/common/windows.ts | 11 + .../platform/windows/electron-main/windows.ts | 37 +- .../electron-main/windowsMainService.ts} | 362 +--- .../electron-main/windowsStateStorage.ts | 2 +- src/vs/platform/windows/node/window.ts | 2 +- .../platform/workspaces/common/workspaces.ts | 7 +- .../workspacesHistoryMainService.ts | 4 +- .../electron-main/workspacesMainService.ts | 11 +- .../electron-main/workspacesService.ts | 2 +- src/vs/vscode.d.ts | 56 +- src/vs/vscode.proposed.d.ts | 93 +- .../api/browser/mainThreadCodeInsets.ts | 3 +- .../workbench/api/browser/mainThreadEditor.ts | 25 +- .../api/browser/mainThreadLanguageFeatures.ts | 12 +- src/vs/workbench/api/browser/mainThreadSCM.ts | 2 +- .../api/browser/mainThreadSaveParticipant.ts | 20 +- .../api/browser/mainThreadStatusBar.ts | 6 +- .../workbench/api/browser/mainThreadTask.ts | 30 +- .../workbench/api/browser/mainThreadUrls.ts | 4 +- .../api/browser/mainThreadWebview.ts | 238 +-- src/vs/workbench/api/browser/media/test.svg | 11 +- src/vs/workbench/api/common/apiCommands.ts | 14 +- .../api/common/configurationExtensionPoint.ts | 8 +- .../workbench/api/common/extHost.api.impl.ts | 14 +- .../workbench/api/common/extHost.protocol.ts | 14 +- .../api/common/extHostApiCommands.ts | 20 +- .../api/common/extHostDiagnostics.ts | 2 +- .../api/common/extHostDocumentData.ts | 14 +- .../api/common/extHostLanguageFeatures.ts | 4 +- .../workbench/api/common/extHostQuickOpen.ts | 32 +- src/vs/workbench/api/common/extHostSCM.ts | 28 +- .../workbench/api/common/extHostStatusBar.ts | 24 +- src/vs/workbench/api/common/extHostTask.ts | 58 +- .../workbench/api/common/extHostTreeViews.ts | 8 +- .../api/common/extHostTypeConverters.ts | 18 +- src/vs/workbench/api/common/extHostTypes.ts | 81 +- src/vs/workbench/api/common/extHostWebview.ts | 66 +- .../api/common/menusExtensionPoint.ts | 8 +- src/vs/workbench/api/common/shared/tasks.ts | 6 +- .../workbench/api/node/extHostDebugService.ts | 10 +- src/vs/workbench/api/node/extHostTask.ts | 10 +- .../api/worker/extHostExtensionService.ts | 4 +- src/vs/workbench/browser/actions.ts | 24 +- .../browser/actions/developerActions.ts | 6 +- .../browser/actions/layoutActions.ts | 30 +- .../workbench/browser/actions/listCommands.ts | 73 +- .../browser/actions/navigationActions.ts | 18 +- .../browser/actions/windowActions.ts | 10 +- .../browser/actions/workspaceActions.ts | 20 +- src/vs/workbench/browser/composite.ts | 44 +- src/vs/workbench/browser/contextkeys.ts | 79 +- src/vs/workbench/browser/dnd.ts | 16 +- src/vs/workbench/browser/editor.ts | 9 +- src/vs/workbench/browser/labels.ts | 12 +- src/vs/workbench/browser/layout.ts | 128 +- src/vs/workbench/browser/panel.ts | 12 +- src/vs/workbench/browser/part.ts | 42 +- .../parts/activitybar/activitybarActions.ts | 39 +- .../parts/activitybar/activitybarPart.ts | 108 +- .../activitybar/media/activityaction.css | 36 + .../workbench/browser/parts/compositeBar.ts | 67 +- .../browser/parts/compositeBarActions.ts | 80 +- .../workbench/browser/parts/compositePart.ts | 46 +- .../browser/parts/editor/binaryEditor.ts | 27 +- .../browser/parts/editor/breadcrumbs.ts | 144 +- .../parts/editor/breadcrumbsControl.ts | 18 +- .../browser/parts/editor/breadcrumbsModel.ts | 30 +- .../browser/parts/editor/breadcrumbsPicker.ts | 8 +- .../parts/editor/editor.contribution.ts | 15 +- .../workbench/browser/parts/editor/editor.ts | 3 +- .../browser/parts/editor/editorActions.ts | 2 +- .../browser/parts/editor/editorCommands.ts | 10 +- .../browser/parts/editor/editorControl.ts | 14 +- .../browser/parts/editor/editorDropTarget.ts | 145 +- .../browser/parts/editor/editorGroupView.ts | 247 ++- .../browser/parts/editor/editorPart.ts | 74 +- .../browser/parts/editor/editorPicker.ts | 4 +- .../browser/parts/editor/editorStatus.ts | 90 +- .../browser/parts/editor/editorWidgets.ts | 20 +- .../parts/editor/media/tabstitlecontrol.css | 15 +- .../parts/editor/noTabsTitleControl.ts | 60 +- .../browser/parts/editor/resourceViewer.ts | 10 +- .../browser/parts/editor/sideBySideEditor.ts | 28 +- .../browser/parts/editor/tabsTitleControl.ts | 284 +-- .../browser/parts/editor/textDiffEditor.ts | 28 +- .../browser/parts/editor/textEditor.ts | 49 +- .../parts/editor/textResourceEditor.ts | 30 +- .../browser/parts/editor/titleControl.ts | 17 +- .../notifications/media/notificationsList.css | 4 +- .../notifications/notificationsActions.ts | 4 +- .../notifications/notificationsCenter.ts | 68 +- .../parts/notifications/notificationsList.ts | 52 +- .../notifications/notificationsToasts.ts | 53 +- .../browser/parts/panel/panelActions.ts | 8 +- .../browser/parts/panel/panelPart.ts | 59 +- .../browser/parts/quickinput/quickInput.ts | 16 +- .../parts/quickinput/quickInputList.ts | 16 +- .../parts/quickopen/quickOpenController.ts | 91 +- .../browser/parts/sidebar/sidebarPart.ts | 50 +- .../parts/statusbar/media/statusbarpart.css | 3 +- .../browser/parts/statusbar/statusbarPart.ts | 49 +- .../parts/titlebar/media/titlebarpart.css | 43 +- .../browser/parts/titlebar/menubarControl.ts | 88 +- .../browser/parts/titlebar/titlebarPart.ts | 123 +- .../browser/parts/views/customView.ts | 39 +- .../browser/parts/views/panelViewlet.ts | 50 +- src/vs/workbench/browser/parts/views/views.ts | 40 +- .../browser/parts/views/viewsViewlet.ts | 3 + src/vs/workbench/browser/quickopen.ts | 32 +- src/vs/workbench/browser/viewlet.ts | 12 +- src/vs/workbench/browser/web.main.ts | 12 +- .../browser/workbench.contribution.ts | 15 + src/vs/workbench/browser/workbench.ts | 42 +- src/vs/workbench/common/actions.ts | 10 +- src/vs/workbench/common/contributions.ts | 8 +- src/vs/workbench/common/editor.ts | 60 +- .../common/editor/binaryEditorModel.ts | 17 +- .../common/editor/diffEditorModel.ts | 3 +- src/vs/workbench/common/editor/editorGroup.ts | 8 +- .../common/editor/resourceEditorInput.ts | 5 +- .../common/editor/textDiffEditorModel.ts | 9 +- .../common/editor/untitledEditorInput.ts | 8 +- .../common/editor/untitledEditorModel.ts | 12 +- src/vs/workbench/common/notifications.ts | 67 +- src/vs/workbench/common/resources.ts | 28 +- src/vs/workbench/common/theme.ts | 40 +- src/vs/workbench/common/viewlet.ts | 2 +- src/vs/workbench/common/views.ts | 1 - .../browser/callHierarchy.contribution.ts | 10 +- .../callHierarchy/browser/callHierarchy.ts | 8 +- .../browser/callHierarchyPeek.ts | 22 +- .../browser/callHierarchyTree.ts | 14 +- .../contrib/cli/node/cli.contribution.ts | 4 +- .../browser/accessibility/accessibility.ts | 8 +- .../browser/codeEditor.contribution.ts | 2 +- .../codeEditor/browser/diffEditorHelper.ts | 106 + .../inspectTMScopes/inspectTMScopes.ts | 8 +- .../languageConfigurationExtensionPoint.ts | 11 +- .../browser/largeFileOptimizations.ts | 8 +- .../codeEditor/browser/menuPreventer.ts | 8 +- .../codeEditor/browser/selectionClipboard.ts | 112 +- .../codeEditor/browser/simpleEditorOptions.ts | 19 +- .../suggestEnabledInput.css | 2 +- .../suggestEnabledInput.ts | 18 +- .../codeEditor/browser/toggleWordWrap.ts | 8 +- .../browser/workbenchReferenceSearch.ts | 2 +- .../codeEditor.contribution.ts | 1 + .../electron-browser/selectionClipboard.ts | 95 + .../contrib/comments/browser/commentMenus.ts | 9 +- .../comments/browser/commentService.ts | 4 +- .../comments/browser/commentThreadWidget.ts | 56 +- .../browser/commentsEditorContribution.ts | 31 +- .../contrib/comments/browser/commentsPanel.ts | 24 +- .../comments/browser/commentsTreeViewer.ts | 4 +- .../contrib/comments/browser/media/review.css | 4 +- .../comments/browser/reactionsAction.ts | 4 + .../comments/browser/simpleCommentEditor.ts | 10 +- .../contrib/customEditor/browser/commands.ts | 7 +- .../customEditor/browser/customEditorInput.ts | 61 +- .../browser/customEditorInputFactory.ts | 21 +- .../customEditor/browser/customEditors.ts | 169 +- .../customEditor/browser/extensionPoint.ts | 26 +- .../customEditor/common/customEditor.ts | 8 +- .../browser/breakpointEditorContribution.ts | 58 +- .../contrib/debug/browser/breakpointWidget.ts | 7 +- .../contrib/debug/browser/breakpointsView.ts | 24 +- .../contrib/debug/browser/callStackView.ts | 9 + .../debug/browser/debug.contribution.ts | 6 + .../debug/browser/debugActionViewItems.ts | 6 +- .../contrib/debug/browser/debugActions.ts | 112 +- .../browser/debugCallStackContribution.ts | 2 +- .../contrib/debug/browser/debugCommands.ts | 58 +- .../browser/debugConfigurationManager.ts | 214 +- .../debug/browser/debugEditorActions.ts | 61 +- .../debug/browser/debugEditorContribution.ts | 72 +- .../contrib/debug/browser/debugHover.ts | 6 +- .../contrib/debug/browser/debugService.ts | 659 +++--- .../contrib/debug/browser/debugSession.ts | 598 +++--- .../contrib/debug/browser/debugToolBar.ts | 6 +- .../contrib/debug/browser/debugViewlet.ts | 2 +- .../browser/extensionHostDebugService.ts | 105 +- .../debug/browser/loadedScriptsView.ts | 2 +- .../browser/media/debug.contribution.css | 8 +- .../debug/browser/media/debugHover.css | 1 + .../contrib/debug/browser/rawDebugSession.ts | 189 +- .../workbench/contrib/debug/browser/repl.ts | 94 +- .../debug/browser/statusbarColorProvider.ts | 7 +- .../contrib/debug/browser/variablesView.ts | 30 +- .../debug/browser/watchExpressionsView.ts | 17 +- .../workbench/contrib/debug/common/debug.ts | 5 +- .../contrib/debug/common/debugModel.ts | 71 +- .../contrib/debug/common/debugUtils.ts | 24 +- .../extensionHostDebugService.ts | 3 +- .../contrib/debug/node/debugAdapter.ts | 2 - .../workbench/contrib/debug/node/terminals.ts | 2 +- .../test/browser/debugANSIHandling.test.ts | 2 +- .../debug/test/browser/debugModel.test.ts | 2 +- .../contrib/debug/test/common/mockDebug.ts | 3 +- .../extensions/browser/extensionEditor.ts | 33 +- .../browser/extensionTipsService.ts | 9 +- .../extensions/browser/extensionsActions.ts | 74 +- .../extensions/browser/extensionsViewer.ts | 2 +- .../extensions/browser/extensionsViews.ts | 10 +- .../extensions/browser/extensionsWidgets.ts | 6 +- .../browser/extensionsWorkbenchService.ts | 45 +- .../browser/media/extensionEditor.css | 2 +- .../browser/media/extensionsViewlet.css | 15 +- .../browser/media/language-icon.svg | 0 .../extensionProfileService.ts | 4 +- .../electron-browser/extensionsActions.ts | 2 +- .../extensionsAutoProfiler.ts | 2 +- .../runtimeExtensionsEditor.ts | 34 +- .../extensionsTipsService.test.ts | 1 - .../extensionsWorkbenchService.test.ts | 40 + .../browser/externalTerminal.contribution.ts | 2 +- .../contrib/feedback/browser/feedback.ts | 10 +- .../feedback/browser/feedbackStatusbarItem.ts | 6 +- .../browser/editors/fileEditorTracker.ts | 6 +- .../files/browser/editors/textFileEditor.ts | 16 +- .../contrib/files/browser/explorerViewlet.ts | 2 +- .../contrib/files/browser/fileActions.ts | 310 ++- .../contrib/files/browser/fileCommands.ts | 16 +- .../files/browser/files.contribution.ts | 7 +- .../workbench/contrib/files/browser/files.ts | 4 +- .../contrib/files/browser/saveErrorHandler.ts | 66 +- .../contrib/files/browser/views/emptyView.ts | 28 +- .../files/browser/views/explorerView.ts | 9 +- .../files/browser/views/explorerViewer.ts | 173 +- .../files/browser/views/openEditorsView.ts | 12 +- .../contrib/files/common/dirtyFilesTracker.ts | 2 +- .../files/common/editors/fileEditorInput.ts | 14 +- .../contrib/files/common/explorerService.ts | 4 +- .../workbench/contrib/files/common/files.ts | 4 +- .../contrib/files/common/workspaceWatcher.ts | 2 +- .../format/browser/formatActionsMultiple.ts | 10 +- .../browser/localizations.contribution.ts | 46 +- .../browser/localizationsActions.ts | 2 +- .../contrib/logs/common/logsActions.ts | 8 +- .../logs/electron-browser/logsActions.ts | 4 +- .../markers/browser/markersPanelActions.ts | 10 +- .../markers/browser/markersTreeViewer.ts | 4 +- .../contrib/markers/browser/media/markers.css | 10 +- .../outline/browser/outline.contribution.ts | 130 ++ .../contrib/outline/browser/outlinePanel.ts | 42 +- .../contrib/output/browser/outputActions.ts | 8 +- .../contrib/output/browser/outputServices.ts | 2 +- .../output/common/outputLinkComputer.ts | 11 +- .../preferences/browser/keybindingWidgets.ts | 83 +- .../preferences/browser/keybindingsEditor.ts | 13 +- .../browser/keybindingsEditorContribution.ts | 31 +- .../browser/media/settingsEditor2.css | 2 +- .../preferences/browser/preferencesActions.ts | 14 +- .../preferences/browser/preferencesEditor.ts | 46 +- .../browser/preferencesRenderers.ts | 10 +- .../preferences/browser/preferencesWidgets.ts | 17 +- .../preferences/browser/settingsEditor2.ts | 112 +- .../preferences/browser/settingsLayout.ts | 7 +- .../preferences/browser/settingsTree.ts | 44 +- .../preferences/browser/settingsTreeModels.ts | 18 +- .../preferences/browser/settingsWidgets.ts | 5 + .../contrib/preferences/browser/tocTree.ts | 2 +- .../quickopen/browser/commandsHandler.ts | 142 +- .../quickopen/browser/gotoSymbolHandler.ts | 6 +- .../contrib/quickopen/browser/helpHandler.ts | 10 +- .../browser/quickopen.contribution.ts | 2 +- .../quickopen/browser/viewPickerHandler.ts | 2 +- .../browser/relauncher.contribution.ts | 34 +- .../contrib/remote/browser/remote.ts | 6 +- .../contrib/scm/browser/dirtydiffDecorator.ts | 103 +- .../contrib/scm/browser/mainPanel.ts | 9 +- .../contrib/scm/browser/media/scmViewlet.css | 26 +- .../contrib/scm/browser/repositoryPanel.ts | 268 ++- .../contrib/scm/browser/scm.contribution.ts | 21 +- .../contrib/scm/browser/scmViewlet.ts | 2 +- src/vs/workbench/contrib/scm/common/scm.ts | 4 +- .../search/browser/media/searchview.css | 17 +- .../search/browser/openAnythingHandler.ts | 2 +- .../contrib/search/browser/openFileHandler.ts | 24 +- .../search/browser/openSymbolHandler.ts | 12 +- .../contrib/search/browser/replaceService.ts | 2 +- .../search/browser/search.contribution.ts | 21 +- .../contrib/search/browser/searchActions.ts | 2 +- .../contrib/search/browser/searchView.ts | 8 +- .../contrib/search/common/searchModel.ts | 2 +- .../contrib/snippets/browser/tabCompletion.ts | 10 +- .../partsSplash.contribution.ts | 11 +- .../stats/electron-browser/workspaceStats.ts | 22 + .../tasks/browser/abstractTaskService.ts | 218 +- .../tasks/browser/task.contribution.ts | 65 +- .../tasks/browser/terminalTaskSystem.ts | 55 +- .../contrib/tasks/common/jsonSchemaCommon.ts | 50 +- .../contrib/tasks/common/problemCollectors.ts | 4 + .../contrib/tasks/common/taskConfiguration.ts | 103 +- .../contrib/tasks/common/taskService.ts | 5 +- .../contrib/tasks/common/taskSystem.ts | 3 +- .../workbench/contrib/tasks/common/tasks.ts | 18 +- .../contrib/tasks/node/processTaskSystem.ts | 4 + .../browser/addons/navigationModeAddon.ts | 4 +- .../terminal/browser/terminal.contribution.ts | 5 + .../contrib/terminal/browser/terminal.ts | 5 +- .../terminal/browser/terminalInstance.ts | 52 +- .../contrib/terminal/browser/terminalPanel.ts | 4 +- .../terminal/browser/terminalService.ts | 27 +- .../contrib/terminal/browser/terminalTab.ts | 12 +- .../contrib/terminal/common/terminal.ts | 2 + .../terminal/common/terminalEnvironment.ts | 1 - .../contrib/terminal/node/terminalProcess.ts | 9 +- .../terminalCommandTracker.test.ts | 134 +- .../themes/browser/themes.contribution.ts | 105 +- .../update/browser/releaseNotesEditor.ts | 8 +- .../contrib/update/browser/update.ts | 10 +- .../electron-browser/update.contribution.ts | 18 - .../contrib/update/electron-browser/update.ts | 40 - .../trustedDomainsFileSystemProvider.ts | 24 +- .../contrib/url/common/url.contribution.ts | 2 +- .../userDataSync/browser/userDataSync.ts | 2 +- .../contrib/watermark/browser/watermark.ts | 7 +- .../webview/browser/baseWebviewElement.ts | 290 +++ .../browser/dynamicWebviewEditorOverlay.ts | 71 +- .../contrib/webview/browser/pre/main.js | 5 +- .../webview/browser/webview.contribution.ts | 4 +- .../contrib/webview/browser/webview.ts | 21 +- .../contrib/webview/browser/webviewEditor.ts | 71 +- .../webview/browser/webviewEditorInput.ts | 51 +- .../browser/webviewEditorInputFactory.ts | 8 +- .../contrib/webview/browser/webviewElement.ts | 295 +-- .../contrib/webview/browser/webviewService.ts | 17 +- ...rService.ts => webviewWorkbenchService.ts} | 227 +-- .../contrib/webview/common/portMapping.ts | 3 + .../contrib/webview/common/resourceLoader.ts | 2 +- .../contrib/webview/common/themeing.ts | 104 +- .../electron-browser/pre/electron-index.js | 9 - .../electron-browser/webviewElement.ts | 573 ++---- .../electron-browser/webviewService.ts | 13 +- .../electron-browser/openWebsite.ts | 72 - .../welcome/overlay/browser/welcomeOverlay.ts | 6 +- .../welcome/page/browser/welcomePage.ts | 1 - .../browser/telemetryOptOut.contribution.ts} | 2 +- .../browser/telemetryOptOut.ts | 90 +- .../telemetryOptOut.contribution.ts} | 4 +- .../electron-browser/telemetryOptOut.ts | 4 +- .../editor/vs_code_editor_walkthrough.ts | 2 +- .../walkThrough/browser/walkThroughInput.ts | 5 +- .../actions/developerActions.ts | 37 +- .../electron-browser/actions/windowActions.ts | 12 +- .../actions/workspaceActions.ts | 2 +- .../electron-browser/desktop.contribution.ts | 39 +- .../electron-browser/desktop.main.ts | 2 +- src/vs/workbench/electron-browser/window.ts | 25 +- .../node/accessibilityService.ts | 17 +- .../electron-browser/clipboardService.ts | 2 +- .../configuration/browser/configuration.ts | 4 +- .../browser/configurationService.ts | 8 +- .../common/configurationEditingService.ts | 5 - .../common/configurationModels.ts | 7 + .../configurationEditingService.test.ts | 2 +- .../configurationService.test.ts | 65 +- .../browser/configurationResolverService.ts | 18 +- .../common/configurationResolver.ts | 2 +- .../common/variableResolver.ts | 85 +- .../configurationResolverService.test.ts | 2 +- .../electron-browser/contextmenuService.ts | 15 +- .../credentials/browser/credentialsService.ts | 18 +- .../decorations/browser/decorationsService.ts | 50 +- .../test/browser/decorationsService.test.ts | 7 +- .../browser/abstractFileDialogService.ts | 4 +- .../services/dialogs/browser/dialogService.ts | 2 +- .../dialogs/browser/simpleFileDialog.ts | 23 +- .../dialogs/electron-browser/dialogService.ts | 32 +- .../electron-browser/fileDialogService.ts | 18 +- .../editor/browser/codeEditorService.ts | 10 +- .../services/editor/browser/editorService.ts | 13 +- .../test/browser/editorGroupsService.test.ts | 2 +- .../editor/test/browser/editorService.test.ts | 2 +- .../environment/browser/environmentService.ts | 141 +- .../environment/common/environmentService.ts | 1 - .../environment/node/environmentService.ts | 2 - .../common/extensionEnablementService.ts | 13 +- .../extensionEnablementService.test.ts | 91 +- .../extensions/browser/extensionUrlHandler.ts | 118 +- .../browser/webWorkerExtensionHostStarter.ts | 4 + .../common/abstractExtensionService.ts | 11 +- .../common/extensionDescriptionRegistry.ts | 7 +- .../extensions/common/extensionHostMain.ts | 6 +- .../common/extensionHostProcessManager.ts | 9 +- .../services/extensions/common/extensions.ts | 5 +- .../common/remoteExtensionHostClient.ts | 4 + .../services/extensions/common/rpcProtocol.ts | 9 +- .../electron-browser/extensionHost.ts | 104 +- .../electron-browser/extensionService.ts | 4 +- .../node/extensionHostProcessSetup.ts | 16 +- .../extensions/node/extensionPoints.ts | 12 +- .../extensions/worker/extensionHostWorker.ts | 3 - .../services/history/browser/history.ts | 24 +- .../host/browser/browserHostService.ts | 42 +- .../workbench/services/host/browser/host.ts | 6 - .../electron-browser/desktopHostService.ts | 4 - .../integrity/node/integrityService.ts | 2 +- .../keybindingEditing.test.ts | 11 +- .../services/layout/browser/layoutService.ts | 14 +- .../mode/common/workbenchModeService.ts | 2 +- .../common/notificationService.ts | 15 +- .../output/common/outputChannelModel.ts | 2 +- .../services/panel/common/panelService.ts | 12 +- .../services/path/common/remotePathService.ts | 81 + .../preferences/browser/preferencesService.ts | 6 +- .../common/keybindingsEditorModel.ts | 2 + .../preferences/common/preferencesModels.ts | 14 +- .../progress/browser/progressService.ts | 16 +- .../common/abstractRemoteAgentService.ts | 21 + .../common/remoteAgentEnvironmentChannel.ts | 9 + .../remote/common/remoteAgentService.ts | 3 + .../services/remote/node/tunnelService.ts | 2 +- .../services/search/common/search.ts | 4 +- .../search/node/ripgrepTextSearchEngine.ts | 10 +- .../telemetry/browser/telemetryService.ts | 49 +- .../electron-browser/telemetryService.ts | 2 +- .../browser/browserTextFileService.ts | 2 +- .../textfile/browser/textFileService.ts | 20 +- .../textfile/common/textFileEditorModel.ts | 91 +- .../common/textFileEditorModelManager.ts | 12 +- .../common/textResourcePropertiesService.ts | 6 +- .../services/textfile/common/textfiles.ts | 497 ++--- .../electron-browser/nativeTextFileService.ts | 16 +- .../textfile/test/textFileEditorModel.test.ts | 9 +- .../test/textFileEditorModelManager.test.ts | 2 +- .../themes/browser/fileIconThemeStore.ts | 17 +- .../themes/browser/workbenchThemeService.ts | 43 +- .../services/themes/common/colorThemeStore.ts | 12 +- .../untitled/common/untitledEditorService.ts | 4 +- .../services/url/browser/urlService.ts | 2 +- .../url/electron-browser/urlService.ts | 15 +- .../services/viewlet/browser/viewlet.ts | 8 +- .../abstractWorkspaceEditingService.ts | 2 +- .../workspaceEditingService.ts | 2 + .../parts/editor/breadcrumbModel.test.ts | 4 +- .../test/common/editor/editorGroups.test.ts | 2 +- .../test/common/notifications.test.ts | 15 +- .../api/extHostApiCommands.test.ts | 8 +- .../api/extHostConfiguration.test.ts | 2 +- .../api/extHostMessagerService.test.ts | 10 +- .../api/extHostSearch.test.ts | 2 +- .../api/extHostTypeConverter.test.ts | 23 +- .../api/extHostWebview.test.ts | 15 +- .../api/mainThreadDocumentsAndEditors.test.ts | 2 +- .../api/mainThreadEditors.test.ts | 2 +- .../workbench/test/workbenchTestServices.ts | 109 +- src/vs/workbench/workbench.common.main.ts | 1 + src/vs/workbench/workbench.desktop.main.ts | 9 +- src/vs/workbench/workbench.web.api.ts | 2 +- src/vs/workbench/workbench.web.main.ts | 6 +- test/automation/src/keybindings.ts | 2 +- test/automation/src/problems.ts | 2 +- test/automation/src/puppeteerDriver.ts | 14 +- test/automation/src/scm.ts | 4 +- test/automation/src/search.ts | 8 +- test/automation/src/statusbar.ts | 6 +- test/electron/index.js | 15 +- test/smoke/README.md | 7 +- test/smoke/src/areas/git/git.test.ts | 2 + yarn.lock | 172 +- 913 files changed, 18898 insertions(+), 16536 deletions(-) create mode 100644 .github/pull_request_template.md create mode 100644 build/lib/electron.ts create mode 100644 extensions/configuration-editing/schemas/attachContainer.schema.json mode change 100755 => 100644 extensions/git/src/commands.ts mode change 100755 => 100644 resources/win32/inno-big-125.bmp mode change 100755 => 100644 resources/win32/inno-big-150.bmp mode change 100755 => 100644 resources/win32/inno-big-175.bmp mode change 100755 => 100644 resources/win32/inno-big-200.bmp mode change 100755 => 100644 resources/win32/inno-big-225.bmp mode change 100755 => 100644 resources/win32/inno-small-100.bmp mode change 100755 => 100644 resources/win32/inno-small-125.bmp mode change 100755 => 100644 resources/win32/inno-small-150.bmp mode change 100755 => 100644 resources/win32/inno-small-175.bmp mode change 100755 => 100644 resources/win32/inno-small-200.bmp mode change 100755 => 100644 resources/win32/inno-small-225.bmp mode change 100755 => 100644 resources/win32/inno-small-250.bmp create mode 100755 scripts/code-web.js delete mode 100644 src/vs/base/browser/ui/octiconLabel/octiconLabel.mock.ts delete mode 100644 src/vs/base/browser/ui/octiconLabel/octiconLabel.ts delete mode 100644 src/vs/base/browser/ui/octiconLabel/octicons/octicons-animations.css delete mode 100644 src/vs/base/browser/ui/octiconLabel/octicons/octicons.css delete mode 100644 src/vs/base/browser/ui/octiconLabel/octicons/octicons.ttf create mode 100644 src/vs/base/common/codicon.ts create mode 100644 src/vs/base/common/lazy.ts delete mode 100644 src/vs/base/common/octicon.ts rename src/vs/{editor/common/core => base/common}/uint.ts (74%) rename src/vs/base/test/common/{octicon.test.ts => codicon.test.ts} (51%) create mode 100644 src/vs/base/test/common/lazy.test.ts mode change 100755 => 100644 src/vs/code/electron-browser/processExplorer/media/collapsed.svg mode change 100755 => 100644 src/vs/code/electron-browser/processExplorer/media/expanded.svg create mode 100644 src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts create mode 100644 src/vs/editor/browser/viewParts/minimap/minimapCharRendererFactory.ts create mode 100644 src/vs/editor/browser/viewParts/minimap/minimapCharSheet.ts delete mode 100644 src/vs/editor/common/view/minimapCharRenderer.ts delete mode 100644 src/vs/editor/common/view/runtimeMinimapCharRenderer.ts create mode 100644 src/vs/editor/common/viewModel/minimapTokensColorTracker.ts rename src/vs/editor/test/{common => browser}/view/minimapCharRenderer.test.ts (70%) delete mode 100644 src/vs/editor/test/common/view/minimapCharRendererFactory.ts create mode 100644 src/vs/platform/telemetry/test/browser/commonProperties.test.ts rename src/vs/{code/electron-main/windows.ts => platform/windows/electron-main/windowsMainService.ts} (81%) rename src/vs/{code => platform/windows}/electron-main/windowsStateStorage.ts (97%) create mode 100644 src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts create mode 100644 src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts mode change 100755 => 100644 src/vs/workbench/contrib/extensions/browser/media/language-icon.svg delete mode 100644 src/vs/workbench/contrib/update/electron-browser/update.contribution.ts delete mode 100644 src/vs/workbench/contrib/update/electron-browser/update.ts create mode 100644 src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts rename src/vs/workbench/contrib/webview/browser/{webviewEditorService.ts => webviewWorkbenchService.ts} (66%) delete mode 100644 src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/openWebsite.ts rename src/vs/workbench/contrib/welcome/{gettingStarted/browser/gettingStarted.contribution.ts => telemetryOptOut/browser/telemetryOptOut.contribution.ts} (95%) rename src/vs/workbench/contrib/welcome/{gettingStarted => telemetryOptOut}/browser/telemetryOptOut.ts (66%) rename src/vs/workbench/contrib/welcome/{gettingStarted/electron-browser/gettingStarted.contribution.ts => telemetryOptOut/electron-browser/telemetryOptOut.contribution.ts} (71%) rename src/vs/workbench/contrib/welcome/{gettingStarted => telemetryOptOut}/electron-browser/telemetryOptOut.ts (96%) create mode 100644 src/vs/workbench/services/path/common/remotePathService.ts diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..276121a227 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,9 @@ + + +This PR fixes # diff --git a/.vscode/launch.json b/.vscode/launch.json index 223d40bb8d..971df77367 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -128,6 +128,33 @@ "webRoot": "${workspaceFolder}", "timeout": 45000 }, + { + "type": "chrome", + "request": "launch", + "name": "Launch ADS (Web) (TBD)", + "runtimeExecutable": "yarn", + "runtimeArgs": [ + "web" + ], + }, + { + "type": "chrome", + "request": "launch", + "name": "Launch ADS (Web, Chrome) (TBD)", + "url": "http://localhost:8080", + "preLaunchTask": "Run web" + }, + { + "type": "node", + "request": "launch", + "name": "Git Unit Tests", + "program": "${workspaceFolder}/extensions/git/node_modules/mocha/bin/_mocha", + "stopOnEntry": false, + "cwd": "${workspaceFolder}/extensions/git", + "outFiles": [ + "${workspaceFolder}/extensions/git/out/**/*.js" + ] + }, { "name": "Launch Built-in Extension", "type": "extensionHost", diff --git a/.vscode/settings.json b/.vscode/settings.json index 551d18bb0c..30b4aab454 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -60,5 +60,6 @@ "remote.extensionKind": { "msjsdiag.debugger-for-chrome": "workspace" }, + "gulp.autoDetect": "off", "files.insertFinalNewline": true } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 6572a5c756..8e0dcabd44 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -90,8 +90,8 @@ "problemMatcher": [] }, { - "type": "gulp", - "task": "electron", + "type": "npm", + "script": "electron", "label": "Download electron" }, { @@ -99,5 +99,24 @@ "task": "hygiene", "problemMatcher": [] }, + { + "type": "shell", + "command": "yarn web -- --no-launch", + "label": "Run web", + "isBackground": true, + // This section to make error go away when launching the debug config + "problemMatcher": { + "pattern": { + "regexp": "" + }, + "background": { + "beginsPattern": ".*node .*", + "endsPattern": "Web UI available at .*" + } + }, + "presentation": { + "reveal": "never" + } + }, ] } diff --git a/.yarnrc b/.yarnrc index ff946c7a25..c54f7d6d6e 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "4.2.10" +target "6.0.12" runtime "electron" diff --git a/azure-pipelines-linux-mac.yml b/azure-pipelines-linux-mac.yml index dc2f3380f0..24e37525ca 100644 --- a/azure-pipelines-linux-mac.yml +++ b/azure-pipelines-linux-mac.yml @@ -40,7 +40,7 @@ steps: condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - script: | - yarn gulp electron-x64 + yarn electron x64 displayName: Download Electron env: GITHUB_TOKEN: $(GITHUB_TOKEN) diff --git a/azure-pipelines-windows.yml b/azure-pipelines-windows.yml index 4f57abb46f..7a3a0950e2 100644 --- a/azure-pipelines-windows.yml +++ b/azure-pipelines-windows.yml @@ -28,7 +28,7 @@ steps: condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - script: | - yarn gulp electron-x64 + yarn electron x64 displayName: "Electron" env: GITHUB_TOKEN: $(GITHUB_TOKEN) diff --git a/build/azure-pipelines/darwin/continuous-build-darwin.yml b/build/azure-pipelines/darwin/continuous-build-darwin.yml index ef636f91e9..86cf6e4411 100644 --- a/build/azure-pipelines/darwin/continuous-build-darwin.yml +++ b/build/azure-pipelines/darwin/continuous-build-darwin.yml @@ -21,7 +21,7 @@ steps: vstsFeed: '$(ArtifactFeed)' condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - script: | - yarn gulp electron-x64 + yarn electron x64 displayName: Download Electron - script: | yarn gulp hygiene --skip-tslint diff --git a/build/azure-pipelines/linux/continuous-build-linux.yml b/build/azure-pipelines/linux/continuous-build-linux.yml index d71ab286b1..7e62edbe87 100644 --- a/build/azure-pipelines/linux/continuous-build-linux.yml +++ b/build/azure-pipelines/linux/continuous-build-linux.yml @@ -29,7 +29,7 @@ steps: vstsFeed: '$(ArtifactFeed)' condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - script: | - yarn gulp electron-x64 + yarn electron x64 displayName: Download Electron - script: | yarn gulp hygiene --skip-tslint diff --git a/build/azure-pipelines/win32/continuous-build-win32.yml b/build/azure-pipelines/win32/continuous-build-win32.yml index f9b1130d24..dc102a4656 100644 --- a/build/azure-pipelines/win32/continuous-build-win32.yml +++ b/build/azure-pipelines/win32/continuous-build-win32.yml @@ -25,7 +25,7 @@ steps: vstsFeed: '$(ArtifactFeed)' condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - powershell: | - yarn gulp electron + yarn electron - script: | yarn gulp hygiene --skip-tslint displayName: Run Hygiene Checks diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index ae36c8cbe8..3c95d01287 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -107,7 +107,7 @@ steps: - powershell: | . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" - exec { yarn gulp "electron-$(VSCODE_ARCH)" } + exec { yarn electron $(VSCODE_ARCH) } exec { .\scripts\test.bat --build --tfs "Unit Tests" } displayName: Run unit tests condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 7a41817ae3..9054fbd83e 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -84,9 +84,6 @@ const extractEditorSrcTask = task.define('extract-editor-src', () => { `lib.dom.d.ts`, `lib.webworker.importscripts.d.ts` ], - redirects: { - 'vs/base/browser/ui/octiconLabel/octiconLabel': 'vs/base/browser/ui/octiconLabel/octiconLabel.mock', - }, shakeLevel: 2, // 0-Files, 1-InnerFile, 2-ClassMembers importIgnorePattern: /(^vs\/css!)|(promise-polyfill\/polyfill)/, destRoot: path.join(root, 'out-editor-src') diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index f4ce8c9b9a..75110fa153 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -73,7 +73,6 @@ const indentationFilter = [ '!**/yarn-error.log', // except multiple specific folders - '!**/octicons/**', '!**/codicon/**', '!**/fixtures/**', '!**/lib/**', @@ -136,6 +135,7 @@ const copyrightFilter = [ '!extensions/html-language-features/server/src/modes/typescript/*', '!extensions/*/server/bin/*', '!src/vs/editor/test/node/classification/typescript-test.ts', + '!scripts/code-web.js', // {{SQL CARBON EDIT}} '!extensions/notebook/src/intellisense/text.ts', '!extensions/mssql/src/hdfs/webhdfs.ts', diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 5e8d49bc06..5199112cbd 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -31,7 +31,7 @@ const crypto = require('crypto'); const i18n = require('./lib/i18n'); const ext = require('./lib/extensions'); // {{SQL CARBON EDIT}} const deps = require('./dependencies'); -const getElectronVersion = require('./lib/electron').getElectronVersion; +const { config } = require('./lib/electron'); const createAsar = require('./lib/asar').createAsar; const { compileBuildTask } = require('./gulpfile.compile'); const { compileExtensionsBuildTask } = require('./gulpfile.extensions'); @@ -78,7 +78,6 @@ const vscodeResources = [ 'out-build/vs/base/common/performance.js', 'out-build/vs/base/node/languagePacks.js', 'out-build/vs/base/node/{stdForkStart.js,terminateProcess.sh,cpuUsage.sh,ps.sh}', - 'out-build/vs/base/browser/ui/octiconLabel/octicons/**', 'out-build/vs/base/browser/ui/codiconLabel/codicon/**', 'out-build/vs/workbench/browser/media/*-theme.css', 'out-build/vs/workbench/contrib/debug/**/*.json', @@ -144,72 +143,6 @@ const minifyVSCodeTask = task.define('minify-vscode', task.series( )); gulp.task(minifyVSCodeTask); -// Package - -// @ts-ignore JSON checking: darwinCredits is optional -const darwinCreditsTemplate = product.darwinCredits && _.template(fs.readFileSync(path.join(root, product.darwinCredits), 'utf8')); - -function darwinBundleDocumentType(extensions, icon) { - return { - name: product.nameLong + ' document', - role: 'Editor', - ostypes: ["TEXT", "utxt", "TUTX", "****"], - extensions: extensions, - iconFile: icon - }; -} - -const config = { - version: getElectronVersion(), - productAppName: product.nameLong, - companyName: 'Microsoft Corporation', - copyright: 'Copyright (C) 2019 Microsoft. All rights reserved', - darwinIcon: product.quality === 'stable' ? 'resources/darwin/code.icns' : 'resources/darwin/code-insiders.icns', // {{SQL CARBON EDIT}} Use separate icons for non-stable - darwinBundleIdentifier: product.darwinBundleIdentifier, - darwinApplicationCategoryType: 'public.app-category.developer-tools', - darwinHelpBookFolder: 'VS Code HelpBook', - darwinHelpBookName: 'VS Code HelpBook', - darwinBundleDocumentTypes: [ - darwinBundleDocumentType(["csv", "json", "sqlplan", "sql", "xml"], product.quality === 'stable' ? 'resources/darwin/code_file.icns' : 'resources/darwin/code_file-insiders.icns'), // {{SQL CARBON EDIT}} - Remove most document types and replace with ours. Also use separate icon for non-stable - ], - darwinBundleURLTypes: [{ - role: 'Viewer', - name: product.nameLong, - urlSchemes: [product.urlProtocol] - }], - darwinForceDarkModeSupport: true, - darwinCredits: darwinCreditsTemplate ? Buffer.from(darwinCreditsTemplate({ commit: commit, date: new Date().toISOString() })) : undefined, - linuxExecutableName: product.applicationName, - winIcon: product.quality === 'stable' ? 'resources/win32/code.ico' : 'resources/win32/code-insiders.ico', // {{SQL CARBON EDIT}} Use separate icons for non-stable - token: process.env['VSCODE_MIXIN_PASSWORD'] || process.env['GITHUB_TOKEN'] || undefined, - - // @ts-ignore JSON checking: electronRepository is optional - repo: product.electronRepository || undefined -}; - -function getElectron(arch) { - return () => { - const electronOpts = _.extend({}, config, { - platform: process.platform, - arch, - ffmpegChromium: true, - keepDefaultApp: true - }); - - return gulp.src('package.json') - .pipe(json({ name: product.nameShort })) - .pipe(electron(electronOpts)) - .pipe(filter(['**', '!**/app/package.json'])) - .pipe(vfs.dest('.build/electron')); - }; -} - -gulp.task(task.define('electron', task.series(util.rimraf('.build/electron'), getElectron(process.arch)))); -gulp.task(task.define('electron-ia32', task.series(util.rimraf('.build/electron'), getElectron('ia32')))); -gulp.task(task.define('electron-x64', task.series(util.rimraf('.build/electron'), getElectron('x64')))); -gulp.task(task.define('electron-arm', task.series(util.rimraf('.build/electron'), getElectron('armv7l')))); -gulp.task(task.define('electron-arm64', task.series(util.rimraf('.build/electron'), getElectron('arm64')))); - /** * Compute checksums for some files. * diff --git a/build/gulpfile.vscode.linux.js b/build/gulpfile.vscode.linux.js index e3098f9348..7c11a7a524 100644 --- a/build/gulpfile.vscode.linux.js +++ b/build/gulpfile.vscode.linux.js @@ -44,7 +44,7 @@ function prepareDebPackage(arch) { .pipe(replace('@@NAME_SHORT@@', product.nameShort)) .pipe(replace('@@NAME@@', product.applicationName)) .pipe(replace('@@EXEC@@', `/usr/share/${product.applicationName}/${product.applicationName}`)) - .pipe(replace('@@ICON@@', product.linuxIconName)) + .pipe(replace('@@ICON@@', `/usr/share/pixmaps/${product.linuxIconName}.png`)) .pipe(replace('@@URLPROTOCOL@@', product.urlProtocol)); const appdata = gulp.src('resources/linux/code.appdata.xml', { base: '.' }) diff --git a/build/lib/electron.js b/build/lib/electron.js index 2c981fe95c..b2e8a4a55b 100644 --- a/build/lib/electron.js +++ b/build/lib/electron.js @@ -2,29 +2,88 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - 'use strict'; - -const fs = require('fs'); -const path = require('path'); +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = require("fs"); +const path = require("path"); +const vfs = require("vinyl-fs"); +const filter = require("gulp-filter"); +const json = require("gulp-json-editor"); +const _ = require("underscore"); +const util = require("./util"); +const electron = require('gulp-atom-electron'); const root = path.dirname(path.dirname(__dirname)); - +const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); +const commit = util.getVersion(root); function getElectronVersion() { - const yarnrc = fs.readFileSync(path.join(root, '.yarnrc'), 'utf8'); - // @ts-ignore - const target = /^target "(.*)"$/m.exec(yarnrc)[1]; - - return target; + const yarnrc = fs.readFileSync(path.join(root, '.yarnrc'), 'utf8'); + const target = /^target "(.*)"$/m.exec(yarnrc)[1]; + return target; +} +exports.getElectronVersion = getElectronVersion; +const darwinCreditsTemplate = product.darwinCredits && _.template(fs.readFileSync(path.join(root, product.darwinCredits), 'utf8')); +function darwinBundleDocumentType(extensions, icon) { + return { + name: product.nameLong + ' document', + role: 'Editor', + ostypes: ["TEXT", "utxt", "TUTX", "****"], + extensions: extensions, + iconFile: icon + }; +} +exports.config = { + version: getElectronVersion(), + productAppName: product.nameLong, + companyName: 'Microsoft Corporation', + copyright: 'Copyright (C) 2019 Microsoft. All rights reserved', + darwinIcon: product.quality === 'stable' ? 'resources/darwin/code.icns' : 'resources/darwin/code-insiders.icns', + darwinBundleIdentifier: product.darwinBundleIdentifier, + darwinApplicationCategoryType: 'public.app-category.developer-tools', + darwinHelpBookFolder: 'VS Code HelpBook', + darwinHelpBookName: 'VS Code HelpBook', + darwinBundleDocumentTypes: [ + darwinBundleDocumentType(["csv", "json", "sqlplan", "sql", "xml"], product.quality === 'stable' ? 'resources/darwin/code_file.icns' : 'resources/darwin/code_file-insiders.icns'), + ], + darwinBundleURLTypes: [{ + role: 'Viewer', + name: product.nameLong, + urlSchemes: [product.urlProtocol] + }], + darwinForceDarkModeSupport: true, + darwinCredits: darwinCreditsTemplate ? Buffer.from(darwinCreditsTemplate({ commit: commit, date: new Date().toISOString() })) : undefined, + linuxExecutableName: product.applicationName, + winIcon: product.quality === 'stable' ? 'resources/win32/code.ico' : 'resources/win32/code-insiders.ico', + token: process.env['VSCODE_MIXIN_PASSWORD'] || process.env['GITHUB_TOKEN'] || undefined, + repo: product.electronRepository || undefined +}; +function getElectron(arch) { + return () => { + const electronOpts = _.extend({}, exports.config, { + platform: process.platform, + arch, + ffmpegChromium: true, + keepDefaultApp: true + }); + return vfs.src('package.json') + .pipe(json({ name: product.nameShort })) + .pipe(electron(electronOpts)) + .pipe(filter(['**', '!**/app/package.json'])) + .pipe(vfs.dest('.build/electron')); + }; +} +async function main(arch = process.arch) { + const version = getElectronVersion(); + const electronPath = path.join(root, '.build', 'electron'); + const versionFile = path.join(electronPath, 'version'); + const isUpToDate = fs.existsSync(versionFile) && fs.readFileSync(versionFile, 'utf8') === `${version}`; + if (!isUpToDate) { + await util.rimraf(electronPath)(); + await util.streamToPromise(getElectron(arch)()); + } } - -module.exports.getElectronVersion = getElectronVersion; - -// returns 0 if the right version of electron is in .build/electron -// @ts-ignore if (require.main === module) { - const version = getElectronVersion(); - const versionFile = path.join(root, '.build', 'electron', 'version'); - const isUpToDate = fs.existsSync(versionFile) && fs.readFileSync(versionFile, 'utf8') === `${version}`; - - process.exit(isUpToDate ? 0 : 1); + main(process.argv[2]).catch(err => { + console.error(err); + process.exit(1); + }); } diff --git a/build/lib/electron.ts b/build/lib/electron.ts new file mode 100644 index 0000000000..c995ad84f8 --- /dev/null +++ b/build/lib/electron.ts @@ -0,0 +1,100 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as fs from 'fs'; +import * as path from 'path'; +import * as vfs from 'vinyl-fs'; +import * as filter from 'gulp-filter'; +import * as json from 'gulp-json-editor'; +import * as _ from 'underscore'; +import * as util from './util'; + +const electron = require('gulp-atom-electron'); + +const root = path.dirname(path.dirname(__dirname)); +const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); +const commit = util.getVersion(root); + +export function getElectronVersion(): string { + const yarnrc = fs.readFileSync(path.join(root, '.yarnrc'), 'utf8'); + const target = /^target "(.*)"$/m.exec(yarnrc)![1]; + return target; +} + +const darwinCreditsTemplate = product.darwinCredits && _.template(fs.readFileSync(path.join(root, product.darwinCredits), 'utf8')); + +function darwinBundleDocumentType(extensions: string[], icon: string) { + return { + name: product.nameLong + ' document', + role: 'Editor', + ostypes: ["TEXT", "utxt", "TUTX", "****"], + extensions: extensions, + iconFile: icon + }; +} + +export const config = { + version: getElectronVersion(), + productAppName: product.nameLong, + companyName: 'Microsoft Corporation', + copyright: 'Copyright (C) 2019 Microsoft. All rights reserved', + darwinIcon: product.quality === 'stable' ? 'resources/darwin/code.icns' : 'resources/darwin/code-insiders.icns', // {{SQL CARBON EDIT}} Use separate icons for non-stable + darwinBundleIdentifier: product.darwinBundleIdentifier, + darwinApplicationCategoryType: 'public.app-category.developer-tools', + darwinHelpBookFolder: 'VS Code HelpBook', + darwinHelpBookName: 'VS Code HelpBook', + darwinBundleDocumentTypes: [ + darwinBundleDocumentType(["csv", "json", "sqlplan", "sql", "xml"], product.quality === 'stable' ? 'resources/darwin/code_file.icns' : 'resources/darwin/code_file-insiders.icns'), // {{SQL CARBON EDIT}} - Remove most document types and replace with ours. Also use separate icon for non-stable + ], + darwinBundleURLTypes: [{ + role: 'Viewer', + name: product.nameLong, + urlSchemes: [product.urlProtocol] + }], + darwinForceDarkModeSupport: true, + darwinCredits: darwinCreditsTemplate ? Buffer.from(darwinCreditsTemplate({ commit: commit, date: new Date().toISOString() })) : undefined, + linuxExecutableName: product.applicationName, + winIcon: product.quality === 'stable' ? 'resources/win32/code.ico' : 'resources/win32/code-insiders.ico', // {{SQL CARBON EDIT}} Use separate icons for non-stable + token: process.env['VSCODE_MIXIN_PASSWORD'] || process.env['GITHUB_TOKEN'] || undefined, + repo: product.electronRepository || undefined +}; + +function getElectron(arch: string): () => NodeJS.ReadWriteStream { + return () => { + const electronOpts = _.extend({}, config, { + platform: process.platform, + arch, + ffmpegChromium: true, + keepDefaultApp: true + }); + + return vfs.src('package.json') + .pipe(json({ name: product.nameShort })) + .pipe(electron(electronOpts)) + .pipe(filter(['**', '!**/app/package.json'])) + .pipe(vfs.dest('.build/electron')); + }; +} + +async function main(arch = process.arch): Promise { + const version = getElectronVersion(); + const electronPath = path.join(root, '.build', 'electron'); + const versionFile = path.join(electronPath, 'version'); + const isUpToDate = fs.existsSync(versionFile) && fs.readFileSync(versionFile, 'utf8') === `${version}`; + + if (!isUpToDate) { + await util.rimraf(electronPath)(); + await util.streamToPromise(getElectron(arch)()); + } +} + +if (require.main === module) { + main(process.argv[2]).catch(err => { + console.error(err); + process.exit(1); + }); +} diff --git a/build/lib/util.js b/build/lib/util.js index 04fd9f3ec4..ea7b512e63 100644 --- a/build/lib/util.js +++ b/build/lib/util.js @@ -166,20 +166,23 @@ function stripSourceMappingURL() { } exports.stripSourceMappingURL = stripSourceMappingURL; function rimraf(dir) { - let retries = 0; - const retry = (cb) => { - _rimraf(dir, { maxBusyTries: 1 }, (err) => { - if (!err) { - return cb(); - } - if (err.code === 'ENOTEMPTY' && ++retries < 5) { - return setTimeout(() => retry(cb), 10); - } - return cb(err); - }); - }; - retry.taskName = `clean-${path.basename(dir).toLowerCase()}`; - return retry; + const result = () => new Promise((c, e) => { + let retries = 0; + const retry = () => { + _rimraf(dir, { maxBusyTries: 1 }, (err) => { + if (!err) { + return c(); + } + if (err.code === 'ENOTEMPTY' && ++retries < 5) { + return setTimeout(() => retry(), 10); + } + return e(err); + }); + }; + retry(); + }); + result.taskName = `clean-${path.basename(dir).toLowerCase()}`; + return result; } exports.rimraf = rimraf; function getVersion(root) { @@ -219,3 +222,10 @@ function versionStringToNumber(versionStr) { return parseInt(match[1], 10) * 1e4 + parseInt(match[2], 10) * 1e2 + parseInt(match[3], 10); } exports.versionStringToNumber = versionStringToNumber; +function streamToPromise(stream) { + return new Promise((c, e) => { + stream.on('error', err => e(err)); + stream.on('end', () => c()); + }); +} +exports.streamToPromise = streamToPromise; diff --git a/build/lib/util.ts b/build/lib/util.ts index 974c20ef07..9b63c9fd7e 100644 --- a/build/lib/util.ts +++ b/build/lib/util.ts @@ -218,24 +218,29 @@ export function stripSourceMappingURL(): NodeJS.ReadWriteStream { return es.duplex(input, output); } -export function rimraf(dir: string): (cb: any) => void { - let retries = 0; +export function rimraf(dir: string): () => Promise { + const result = () => new Promise((c, e) => { + let retries = 0; - const retry = (cb: (err?: any) => void) => { - _rimraf(dir, { maxBusyTries: 1 }, (err: any) => { - if (!err) { - return cb(); - } + const retry = () => { + _rimraf(dir, { maxBusyTries: 1 }, (err: any) => { + if (!err) { + return c(); + } - if (err.code === 'ENOTEMPTY' && ++retries < 5) { - return setTimeout(() => retry(cb), 10); - } + if (err.code === 'ENOTEMPTY' && ++retries < 5) { + return setTimeout(() => retry(), 10); + } - return cb(err); - }); - }; - retry.taskName = `clean-${path.basename(dir).toLowerCase()}`; - return retry; + return e(err); + }); + }; + + retry(); + }); + + result.taskName = `clean-${path.basename(dir).toLowerCase()}`; + return result; } export function getVersion(root: string): string | undefined { @@ -281,3 +286,10 @@ export function versionStringToNumber(versionStr: string) { return parseInt(match[1], 10) * 1e4 + parseInt(match[2], 10) * 1e2 + parseInt(match[3], 10); } + +export function streamToPromise(stream: NodeJS.ReadWriteStream): Promise { + return new Promise((c, e) => { + stream.on('error', err => e(err)); + stream.on('end', () => c()); + }); +} diff --git a/build/monaco/api.js b/build/monaco/api.js index a81b4db3e5..8b74d23d62 100644 --- a/build/monaco/api.js +++ b/build/monaco/api.js @@ -516,7 +516,7 @@ class DeclarationResolver { 'file.ts': fileContents }; const service = ts.createLanguageService(new TypeScriptLanguageServiceHost({}, fileMap, {})); - const text = service.getEmitOutput('file.ts', true).outputFiles[0].text; + const text = service.getEmitOutput('file.ts', true, true).outputFiles[0].text; return new CacheEntry(ts.createSourceFile(fileName, text, ts.ScriptTarget.ES5), mtime); } } diff --git a/build/monaco/api.ts b/build/monaco/api.ts index 221cd06975..9572655db5 100644 --- a/build/monaco/api.ts +++ b/build/monaco/api.ts @@ -617,7 +617,7 @@ export class DeclarationResolver { 'file.ts': fileContents }; const service = ts.createLanguageService(new TypeScriptLanguageServiceHost({}, fileMap, {})); - const text = service.getEmitOutput('file.ts', true).outputFiles[0].text; + const text = service.getEmitOutput('file.ts', true, true).outputFiles[0].text; return new CacheEntry( ts.createSourceFile(fileName, text, ts.ScriptTarget.ES5), mtime diff --git a/build/package.json b/build/package.json index 5648107cd2..50116ab295 100644 --- a/build/package.json +++ b/build/package.json @@ -44,10 +44,10 @@ "rollup": "^1.20.3", "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-node-resolve": "^5.2.0", - "terser": "4.3.1", + "terser": "4.3.8", "tslint": "^5.9.1", "service-downloader": "github:anthonydresser/service-downloader#0.1.7", - "typescript": "3.6.2", + "typescript": "3.7.0-dev.20191017", "vsce": "1.48.0", "vscode-telemetry-extractor": "^1.5.4", "xml2js": "^0.4.17" diff --git a/build/win32/Cargo.lock b/build/win32/Cargo.lock index 3c95fb34cd..e27f58bf45 100644 --- a/build/win32/Cargo.lock +++ b/build/win32/Cargo.lock @@ -29,7 +29,7 @@ dependencies = [ [[package]] name = "inno_updater" -version = "0.8.0" +version = "0.8.2" dependencies = [ "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "crc 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/build/win32/inno_updater.exe b/build/win32/inno_updater.exe index baca28361d147d072e1c1144eddd5065877d40ec..8c532b9eb5eab2fe843d29b8d488676263e4c1d7 100644 GIT binary patch literal 403456 zcmeFa3wTw<)&GAI4tT`Cjv6&;ln6%+N;OfcM56`>7Zo)K22{KjX+)|BCyGina8BB6 z52vLqZEdC1)?Tb_-xmvV5i1GU1kh4Y%he)kt8+YF5Ut#l{6F8>d!Hm+dVAmJ|NNfk z_saukuRXJ7X3d(l)~s1Gd+#f*n;q&B3WdV_Z)*#M7IW2KrTzWKKareJsAsQddxjPq z{>M`m=ZyH{DWhv|pISV5%3WWca?4kWzjVu;cit5%zV)`^DRp-i-+pIt)m5X4zjD|3 z+s^9IqkGh^`pIo?J@?*>)Q7?UzwJKx!?(GAK0WJ$5A3z^gI)IOez476zwyDlT-|qO zeegG~@6DL?;asjU<9_hIy-$6pa`%`iAL{-d^G%z6_3dA(RU3!uQT32eXhcre(4$v9 zc|(A+Ez~>bh@77Jp`XH?nRJ%Fr+*Hd56~ozReEr4DAbKR{RP+1%|M{^db?XFH&i*p zBC4DU+iUis#L)DsyM|s9Fu7}J#6ki4{grkL<()>eS9A@H-Y-kHi?8e&D&$(owNeSe zA9TLzkshRUx}FugZCZ?!oj>vWrM{|b9sY_#p_|W|GX9p>EuqlKKPE$cf1K+tIwMxX zkh46r&@5%$+%-h!L!qHuJN#8r!&y_NPWcjPwy*Tr3f}Mo@G8%ma@(Z4KvdsD@E}y8 zinw<8s|U?spF?Sn-nnt{F9^X2yF7YDoSjI#-uj(MNO{ z5_&SL;A^4hS|@HsaZ<)0o3xtrpfLe9*qk+artl!VX(rS`m%!`jYd_^^{KU;U{@y z^3rfwTPhJ1DNe6?enak>=5*Nk+4`dTHy!tt#tro^me#k9so&GxS@?>x@O5X<=3(xR z!`C#9ZA(?Q)va5iLVb77*o^{*oVd(~n|@aMG^RQ;&i$b5{nT@Q^?rXhHD{>5xhU$m z%NtwjSC`heoL@oh%bkVmok44dxGN9e)HtRs^-x>gD_-sCp!RR6_K$4s*NmB%Tbns% zNGLODnfOW19_;HZTIsHAHo3PqS+1EQ?^rv&?>aaYFvqeb-nuWotNT|E50^GpRdl&3 zlkY4_gv7cDZdFAtxzDRYzZa>hig4e3HPsAK)#XD%=J(yYgyJvc&v-$-x$&m!Z&>kc zJ&-x(^e!Rih^%}lEf9=EbdK(h{B$|;(JHX;)8)mUbl9e5?A7=?Gl#=Ao3P~C;})$8 z#$%)Vmz{4kzuV*X<-+Rw2WQbAI(2c^_<;VtpU>DU32|R>7Jcq4dhQaxQ~f`i;7;$q zFLlpn_dXrpWBIhaQxz-6_g(EQTJ0|Hz^DHE;w}69F2ouA)U!}tzr3_@bpN(1)4x|Y z@{yed(Rx64ZC0KUn`RR}gyb|dG3Gb6rS55~+mN}qz4*onX9fCD|CY)(Zf)FLzoxW) z`T6yGk&Ug+!k3*vuMDI7ipCnR{9-lSch^M{ys6iFB3Ba##Q=9RJ$1f$R9jnH`aFK% z(NO_A)H5qIwooeEzwgd5*#Ukn><_T2w*%}pdqaHAsT$%jHOW{sum0lPDZ`QvhLgj> zZRfY;$6pW>pB4_qij$Ybr$r8*8Zk3d;n0{&#N5xd?cUEOhN?{+VDg@@`7+lUC5#8S z@g~o*So31-OZ>f}K3L%H+U`7kW&TA< zaT3EQTE5FkT*7s!P4etP^3FU=TSHUb$;qJ+Tja)|NP#U<_m}h$0PfJd_*cUa^C&lP zBFAZX%7a3!l@TiB64u2@e1&u~o(b?8vEUYfVL>agA7&O}l;X`1^PQ$Hp(MRe=EQg9 zIt{-=InsaSr$aZsW)-WZ+FeUezA=l+ZD;CA()WRgNLGPf5OvquUU$NA5;dTduXPev zs?IwrlSA?vnqsde3*CZfd@sEGFQrclCx=H`?Huh6txVPoZ+O{B?4}{cCG1X*Mv_;K zC|~#RHThF}BwnuT;a)kSWk`NbCY*RVmhawK)fSE>g#>cuwB(gl?!Y`ZZLZzWh5jdl z{sUyzXZ5FHi#slwPt^r9Q5ZWsH6mJI-ub+(Euhg!^hKVNg_g*}4?$#1f)lpHISZlg zc&Ix*pf0~%+a}D0(mbD1QCXj*a{hX#eEwmd%B29A%}%e7hnv0z8sgLP7*y-4wN~bN zV=EJ#9ktl`@m)Ew6A_*vi0<~bNZsgob2$D&k@PBk)Mf_QOTMT>@*P{V$)|Nlp7VM( z+3Apc(Kbo9t#HMXrh!O)Unr=pJ{QR!xZ9WfC6yz%J?CiY?~+6oX&#U%$|ALweoq-) zf{Y2Xds5}maC!vSPx$X?s`o2%5NSKlJnn`=@dqQJ*pX83uW@G;JPvSUNBzcqh`$%1 zP+|PyQ0#>I2lGR91@0T}4znEd@o+d=(dFLW6}hpK+bb&%DmzbYybytVD-f%L8lPUi z@#zZcD2&hNjToP)*7)oKH1G2>Mr!vifSRcLQRX)t`_qwDr6;RsXaq8x-!?g7dY*4t zx<68f3a)YgrY`<<*W&79QS5MZV6tO5>i2S)%N<{^x3 zPpxhTuWJWuESM8Y&5BmY6jfn0hD63xo0|%A#jvt1$pz6$StDrRl2dnBa&EK=sP2nq z%h$u9i4n2nEue-bC((42mEnB1)Eygk2Zmb*Mxy zwO{TBl(UJ|rlwJRiny_8RVo&p21Uc&(a{mDRncnd2yq>)Yi$!%P7KwUP2bbV=B28l zRW_}rdW@NJr_%E4J-&6RyN=qzl#Hm5x=OE8qod)8m1e_-c39Ls5_9UNsCz)k=PXM} zw@h{t7f`gJ%}MlUAz|M58ZweT1<-FJ*O_@I2x68B4-L1S=gn*GZ3#B-0tVM?6tc1gHG(BNQu+%9q2h!%fCl7)G}++ka@O2Y zp%2A=M**W~VI;mQ?8J`|*Gnii@kCWn3M7%06?R*Hay>5U$IgStjPR~KfTOCQsF*fmDrgsAd2Y^@)(0W)n|#D&LM#L^vZE^0 zQW-hYZNwX;YN)C+~L$x*Og@BgDyAICcS4eIz zIA5GS*%q89d<-I)o=~K!KcoKEaD0yhVlrYDM*XCY1DyErV&UK70Sn&;Qr|zv%KtMK zZat8Rb5)UL;?Mkwo=`>YOr+6*fRT4)k?-}9?-BBU$jH6$D`4a6Vq-p))}&tx*jR$U zbrAk{A-SE6Z;6c?W~v}lspjr*s(M$lOmnQ|Uck8`cdHX0BHsN9Y)|~21`_X@v5EhT zcL#{yt*XXyZlYh!?W*SBocm%H`63^=LdgFi=LV>!zOojH0qbXo0mln`5wPtZJ7cy0 znx*{M8FMkf(yda~_nna@sKF#a*KX*%LKqAl8`uNvZ1XKFcS`LAFL=T(s6_``U*Bwr>Oy z8w?it-)6h3YAoBon*mN0Q~X2gUXu3j74nXZweM zu>DOgmhGkn&4Fy*{nY?5$43ka@jqnq zT!gq?x~EH|AEAMo^nC(TfIgcW0b%n4Tr8WX0WAHNSXI={Dy@ZWNl9qDsoqMb6-{RR z+7PCCVu%2peAQ|*A7>gbae{N#srk#RSm56Eg`2(W7bknyi|V;94t>_vM&=9re~JIE z^8d97&Xn!F_*vJDpB+W~e0(B5+m!aEe%|6|OV?ss&z57Vz3X2lI3G59)lB*5WU$^4 zR*zDf`{7CwROyH70Bp9Z`*5?@ric3nvUZ$6p)~+%XbWrI4`1;}+V>8LAD_gJZT+N5 zuU>MeyqpW#YW}bM*m-DHSblB1i2z!Zg{S2UT#-v(E$)z6Z60MI4R;oU(6*#%Ut61& z8R(P#w9N29XW^W@{uC@F1{NW%bZ%mfDj!()_Rh8CD_jrJX_!qZysiqY#<*oiaIHCEr=YKexbOaqv-G9nZ^3Ue{s+|IqNaRNZ< zwoh!7ZN|QV^ea|4u}`gl8uPMU>=Kl58U`|=Gp0ue6rv6fo?z8y>CpifoCd{1(qH69 z%O|;845D2ed6MBTF-u;k3e`sNlhwi7y~-Kjq9p||XXhJEeIGjFuAsffGNdHHSbota4{rk)%QCtNxtCD-5-o!HXZ8xeS+H zsIcMYRNl7-wT+05b^AAR+uw~f5>RT4@IS9{Tq8kgLIe{?JP$_KPD{=8JZ=_+(BkfPH&4uExf!W3pWbiRpYe;;PdZ_)5GIcG3`9BOS&YzX^`M9tuI@nn>B0BKGId)t)U9a+W7ZD2*KxF{{IG9~x=%H=dX zhS^X5hM#3y&KnY1s#!(?kUO2X1^`M?9w$$?sytcco&wVwwW|+5e%H7s8d`*pN%Q6wpB z4H= zJ>sne@fGh8pSvU!`#5>;XXb}>zB&6ofR|hMra~^V0T6aS$q<$=!eT-p=%Msmz4QX5 zkMq*4A->E@w}!aNOK0hjczRD;n>owNv&Or`gR9J`9?}TBwz(jsf5p!dh1@-Bb-`9y zt0kdg5+paKub045z{e=`613L$Fb?w)6h0_>xpo>{HlMIijB6XgJd8p*N4UE^)Ytx^ z+9%9TVp-=O{h$56g0f%vAC#Z*KPda3()|C*wU71P@c%ac*YW=b{@=&{Yx)0G{*UDU zHT=Jw{|)@Vh5s)9JA{lof$j0*Mg-RU7S&B9qQ5~jnN~}PmdtLc64(z=3IX5Ud2B3xwWbT3mlWL9#S zynbMaQUhp@QX+;pS5j)rP18psXTdyb7XZPKg07CP~1ia*B%?Wl3lF;V#GYIitP&QNg!F+Z&0yaft?0R-4@~t z0?fTx#D{#u+k{x!9ze`@TN}10gs4vWA;dEP2_eS%91z5cjqsv3Q~R4DDy+%$Hw9|K zhVhjmTB2$KmvqH48@;#El8&DNIp(hHwDK09*~pLeyZ;Qp+!t>6)M@;*+PX2%kz}3K zKLA)a=C5RUZp?GufOtf^3#?2s;(Bhber89H@lrb6_R$kj9VLUqiRe6liFvh; zBiUw_MUNl?b8R{zu&Ks$>#mKA@F;W9qn?B$qAyX1N+r9HTui%S-AV=s@X*>Z{6&WN zIV-B*1#*g#tfeAS0&zv0?Xec`^a6Kt$w%tdEO)-<2!%q@oO#k5)BzB*gKG^w1=xYc zu(g^RdDzZPr>yRBEE8lA!Yd6Ld9|k$xIJntwZKz_k5CfPXfF7DCw16i?#s)nL*U>_ zDNgp4;@Chbp8f+$QDvh(1*pZFUkTLWT_8!!8(01VwP^X5)xuT9Ui`deABX$-q>g9@CFS}fRkE*H1yDlIUoIWd zMC2|XQ)6D+t7apxR!KQ^ebHGvPt3B)$C6QI0iH-@sY4xAb(y|w!wJ#n3LC^TKF@(U z=ih+~=~*7mSfoH@vrUEB%u-dRI|$xb9|febK4Ancu$4K$T0(aP#eXJ3-HK@J7@Bz; z*t{~)AGNgr45rA%Bqv@bc#!1iSRfmT%2P>LV2zXcaZu<{fb?m~VHX!FI187EQ!N`Z z?>Hc+^f35Ye<8^KLRE|2+ zc*IHQ5QHfRC^-p$PB-iVD?Q+HvUcANL#)f3C3lb}p;- z%Z{b2e+(t7vmHj!X*iEHX?iF>|Av~Cv1DgbVIU+o9o1|lJ+Nxl1b}QR-`-0)&97=9 z&n{b@1`-dpUus8MXbfPKLb?`OwWx}g=-#!CgeTurRmrIu{lPa~7|eeBAC6%3!=NPE zs;ICVaf0E>X=qAShMlTruLy(=%}YKg7sRX16hekIILC+{_HCO~+{>iKV&=Pg8bKC2 zDA^o*GW1O;md^Hho0(Vr^c6aPH8L^RsyQiAeBZH?8IfZLe-hNrq^Cwu=tf;UU({_S zS(-#qDf7*z4^oyq$o9EV%^zeglwk?x!ZD`mJ9e<#_3kEjd5yV^5T?e?Q~kgm_$Gku z8%#IBrNY|L7;;EZgNl@DzIR)y&0V9;3G z21iNpHjYVsgHSrG7O^g;ojeXPXnD0Gyr!$jQPbP$WwrTKYto>!maZ8@=C0or@Y z2Cb3$s?DD~w6afw?1YjP@4_sPGV{D_tkk5g(5;q6QMQtuHg`p}`IeXMr_Z%bk1`K< zsquXrQk&Hss>4x+LLEs$u0{ngTbCu<4D;z|TC$5HSgASD@!+%{pVE1#eSO!*_jS8x zjI-#RvMm!3-tVr+*{Ew`23>L%P08()9J`aGaOb4!N$M6~&N&sYJjdT`6?H$&RN7T` z-{tM+!&b%mU^ep9@pyCYWfPcSGZQ#xH7}+R8YpjehN$>2XS;jNwd1iTg<7B70wjC#%^{v6_8jVkx*xN^yY~2v)XlC> zbd14Yg`A+`)+G1#i$^GKS0ROrfL(K->+ac3#I$9CQ_8!1q@n?*g(4aT19L zl5&vqo^~IZ^PbK}h{rp&O>0SV&w4Mry1bx#R3WckiYjA%fCdcY@ zBmrUPF?tNhg=_-V^(p5-pp#IrPH6lu7-berv3jIVG?5XnAVkzSnC`?*Fo)AlvVYWC zL5)E5Cb56iP7kQGI*7>I%Yvws4+|v~)t|e$#=N@Ci+Rqnsm%96MZJX@-`%yY z_tQBu_PxM(#7=b#~PlDO*J_ z?I`z8<}0`Qrg|cPyIYPxVpZ&ToZRXVXE4GH(`Dq)$-MMC+|jYFb!X1lyGku>au$vX zC%Y_(-F4TbvX${2Qx9Jr`$I=CwyS?MJ9l9(N%wJFc$`r~uUIrcPJqA7Nh&%qRtUvt459F+X-Ey0}l zBOndSYl9%h;~r?!gZWQv>@fc&35BDE!^5ak8&jv7gYBG#L8`((sbc%d8x^}uR8i9} z+faYMp{bo3>UZddhIeSF&^9#O<9owY_>->Z$6ec;ISFU&log+@z`MMVr1+ccx}5_+ zpZlZHDX2nLM9g0q6hMgt?CU5&@LUK?hH}H)HH^bp8C%LzU;r1*gn*yie3~@L`*hVaQH*8E9_G`y_%a)X z5smbrBaJ?BJ`qeIYDE;fE2wKy2Rbp3lgQKDgKVf{WL9$b_;G_(NRtyOn!0@7U>4xw zONVHZKgi53{9tAuWhN&5nH?sTYHm-zYE@5hV#eTRnS!cle%vl+KIn?|{x3VqGtGdnD-IC9=_Wcz(E9^Ee zS@FGX@y#J;@WxT5FSWv{$rz7G-lhXL+J@qEo(@oxY|rP-T+E$$>jrD4(@%R@{msiH zc=O0+AZuAKreBkNPZb{5G6v%VC9(=MojBIk$N7C&4FH*i1OvYO3Cyda_gznkMnKB|{ed9A(^m;aee}n^(0GKn5Ye^1r{{P1-Y`|~=UwlwK4XID z&`g?TUM@^|H}yIl4}wJ9|SQl|XqR9$7;fYuWVQi)`X#2$>WJryGtU zpKGu8<@1jK-pCf1rNFRMR!A<&@4T$a5DrwH9l{Fp2x)fUzw2dIOW5PUcG|B8IM}$p zyo?j={iEqPHJfWd@|Juz07>|6tANacC?c2Vu6SM{O!sK_@^EI1>6^tZ^Kr|;C79!{ z5LbBO#SaTGti1FACydW? z#cX%KrSdWMFULjm8a|DkpPC*W-#$mrPM(|^9JUWR)H+qIt&#}Ne^SOWa5Mm!TkAHm zb6FUQ)i6lY8@AM)lo{dsqvlLd>>gl=aNFFyv9jcan9gabVPS5kN2h03itz{XLa{YT ze6vrl^&9vYAhVRQ>(|g$cU)%ZY_o+FOZY|r+J2q-dQso-fX;sH^*vdXg|o z1R)96U%=?%eTQ1o{l&}1QXO>^}f4*LGE28FC zHAtvkuRRSU34Q(ie`CEiQN^uIn&KB9OYwuO*9K-0FZB`67GkGeqZL~DAT;uM6iw50 zf;(6z(Cxx)FblVMA0*>|lL*EBNrZ^mbPdLVh1)u={=)4gK-0{RwKUHIVA2W@FPURv zkBMj#4KdHWz=e?a_rS0Q>@ffv@_zoj&LM9R#ukwrGUmnXN{bDmr{46&*GsiwIE#!U z%tC=Pz`<2%4qQ`*@ZtUo5} z?P*Fkh0o*dfrYRQDEpLR^`u%E^g>^mqs(_lXL&ap(A_!|I(&CubUc1Ye-yHtK!7Qt ze%gzy@bO9jNjZo|t%)ZV9l>xEL$qC8`0Ek^9r8FZULEMi0o5?cLnSlt+5fp)?(Rna ztoxP0I*u`a;LH{AHn%0kfVdl~%>qB+6`$?yj!`_bbFy6`=3&(}(4JqV?wh(%zJN>t zU~#Ix*6;1&Byj!}O;?9aHF=)t9}7g()0gYSwf*VE^FWpN9FaVN_`V~Ynaijj zX4mV&Ge$l=^i~c3kJkb29%?$+5}?a-hh73K;`Fh<1o#eT76UH}ujngR`?KvtfU-~h z9=hB8l|ak?&Pt#cl_RXxI6otTl|Z$(5*UxW&+%O{yk~U=X)Ul}B%_nQ1Q6G$zYgd8 zft0{?S_W*r!K6&C&M=NOKUWF)RX_DI@$-9{*}&G7NCC9Fd`?3Wx14dY^0eidE}``O zB=9Bx1}K#N4aao?=WCKz(D&Kp(suH}9;2)zpW%L-qKu5Hd=i}j)U-dqQ+Ji$&lyLl z$_KWS3bWL zfq5Yu_;7VcQ(UzB&RlwC#A>EZ^7g?rcyLuc*eFXk*LL6#d>$7qA_!Dn!q4zxekx04 zbCa{8wX{J*XioG=tOmfW=;Id1*NKh%;rTXNG>;_k3&Ech;6D=J&jw(4bhd*{c+d1g zh1fm(-xnco@h0(kJVvMTqRd-tgU|-R9Bb=3F5^Zq=$*tR9QE5CcgVJVrbRg0gCuu| zBb6Ri*+G(R_=ZVQ*&)9of7*vo|Det zPkYQP`}Cm4b58#`ClOlo^auDb&1wa0UA1(Nh$dzR-;9rSq43Plh9rQEaW*f=6U+y*2HT7)a1AWf+xdEtxIjZ89+g2pPLgh(!VaBj(Q=g)gTJo1|?hdvfclx4l|CzY&i@ zQ!+wvKk_yl8oiKR~$hX`>@;*_XKW!9z-bIh;Rw2Ntx`MKom%LfQ zSpksE$b89vzk}Dgq@L#5>XSUwttyvk^C-0~`u!eJu5IIRx!)TcauH}2?ZsbdEeQBa zNK;|8uw$oy%?X+d-$K~sNwp#nE>aEq4kPtD`!w8;R%JK2^W%7nJw^;K8Khr}& zAhZ$2m5neDm0}7C2$+O51heTP-RY0Z5z$PfQx4JCNYKKmX~K@A!VTbrQvK_7BKd^& z=nDIhI)L2rUaYrwJo%>2%--R?w(J5((vF8wx3%GA{G7j#hyaJ>%4`sVjz81hQmZH$ zdfq>gVGGl`9r(V!rJjCBON|1E3BJJ|AcO67JmvS_o3zRA;9H2@I(kWJC4Ba`)g^_( z<05hn?zMNENvxGVA!e0MXIFlL`;UPrz|Js9SD=@n*ztJV(opl}Fx(2dW&6H`8&=d2 zB36UpL0aPC~JhLDc@V# zgi@Xx;l*8*nYnJ0E~9SXr71-!yUz zfz{>g!9NYrONSgom@eM?j-Mp|5sl-)ypF1r4 zm7k<>CRO^RKR~s<@=&dJ5Eki{FJhFEKxxy0j;kmBobNZ#(HYsFGPG2V3=X*n4E#eS zY5fp873ooio!%nm$3qx=LI>aD>W2=#1;{Wj5czg=<^h;GAxcW70F-TUH@bU*C!aXd zF)kWIBGgl#BPt9ij`(2zc2FIndqSvePwg-eLzT+gDnJ`;V9H-H){Ii{=%0Bg7 zX9!iRdLag;5QrCItgZ^C>X(6Jkv5;oBr?F7A{L=|Em(*G-h|UI68%cg;Rn?z?M$!6 zRe$#JMfF2(SZZQ;$C#8~tsadsIgc~q;v8nP`xHb8;^NSz@~}0Ol5Oz2w{}j%dtZ|3+5)t9Zz%3Wy(iNRiFD2 zrM%)3IUKxpf7BGH?nex*trwYdUu4QtYZJrO<|q53-!Jq{?w%SnxZfBvWB>en$j7op zWm4EPf1l%KX6_*0;ranUdb5(|1Gz0`CKu1;dl*O@|8^hhpB$DTwL*h+pGwUHa@&O} zHk|PZH@}&is`z$la)g)U)E(b$ekaUk4C1Yt-*|h@$H^N%Gbd8qdV__khvD%s>bBa$ zjznFLG`j}2PjWkenGQhAr=b|{(P~(D3{t$}6u63?hOr**B-SC*I>N)IbjT&O8zQEV zP4OS>9HnTDo|Z{o8KE{j!&DL$*3F%9@;Yec%%IhR5 zxRHML7e=6*we8A@MqA|!N6p@aR=r;MC%XCSMOoea6QHk~&wGWx{(qisCVp{f-5k)E z)lE4cl5vJgGX4RKvof9m_}|yfUsCITs+*%I>+7bc-9-AN-2wHOVLI@IpaW+Buno-~ zG!o)(HaiX9d;|4p<|kEkj^^q}D$h;ze;BBse0yCa)31D=yDN5D{j~g4tQf-(N=++> zWs+mV=G6=RX=ELM@H4McahUm|r&&yrRr6;6Ql10oTTb^QbH;FjvYz{(hPXLP=rMGC^{x9tMfQc%2Vb{xACA@zD0+9Zbl07nT>g8A976e2k?qG3hUI5WE zFE0Vfyu8Yc=g;15p`Bd4WtB&K!{56xi{6cg;Cm{$%ZdhY1_@_N!KSHW(~CmBkZa!yitIQlL*70#w#4SF(QjJf03;CzWlvwMsjHI^Tz4w(1O3-tOE zAnEmI$6;jm9bvaiAf&@^YQw}oXIK?2l@U_cW(90g{r`#w>T~rV=M3q-FGqB}!zN?E z1@q&_4n50VJtI5I6(G^UJb}HA&@(&Bbp>F<`hR(MvH6R3mXp9^;Kh@`167jefqqq| z3O(8K4A403Gu}nXG3jfy&*(*BcoWYkAocWm zyg6^#DO2FZA%=(HTrmi6u0(N_6I7n#fAA}*+5Y2L1)K`^{WujXEv$bqFIAVHIYUcB zf_J@j&qLF^4J!6t@?x{`Twh-P44~4P^~+{!vrvo2rKHh_jS@bid>c#jXY3hps(r>* zv<>b^FM=8S{oV)6*jIq1P@}Jr2Ru^XB#s9oY2W1P#6On+oKL%>ymz+a@UYT6D;0~5 zPYz)8{X=gYrfX>h73A(h7E2BTU~G5Qt%tEZR>jEPx(O*vpX2lVZ2%A$*~>_0WOeGz zudu_ZHx1@$4dy)XZOy+0$Sg9CbMc1iN!7$q&EpLa*+Yxo(}p^r>G4GZ7*=Yb6qg`4;<&B4NP6LUQt&SaQTFA5`{U-pog^jesGNbHF z%gcRDStT{UkmrYznB?pfKMv*ep0%RdIexYCj${lB0jvFD-rz}<*8-?7iJwziuwbT-s%r${gx=%!lxJ=ARd9FAEFj1peX&yh@(~O6-0QcHBM5~EtfvV8p`DMpbmOupyWG8Ve z<;~e7WP_#s{hGf^coWSng=|T=dTJ*^R#Ha8U0pk#H*lF7eyB+}4;v`%vHIBpouG(;olDl%x0gD7|5C;)B@Q3D z)JH$qudTm(Jkv;9|Y=w&f=kyh|J>yK8^%dQ3m>+9S%<9jZr#hw9<9 z8hPM7%~w^8)#V1inmcITo?$E!soH}VU`A&V$N7i~-FMuB@9DNUyu^!ddkj7N;i~Ax zjYE$($f9SbA|n1PnzsO(z5URU^hSXvP&0wqG%23h{s_nuu)03!PkMl|lN3-|vM6hS z)cf`1c8c8wE5L>=c1z7VD&GV-yQS914faQ|PjA;vGGQilM7hmFkw$qa5&&~)N0dR_ zph%_W5`biT)HZQc9#)|#RzhYN#V>4AJhw2;ahz#P>lO04ruH_UpJ|JSGksV`=%kL_ z1aum9zJ>if0yAN@fI+=s|NT=h>sFhtI1laUD~0|VKfa*z9%wy}$6O<6I><2mR=NH# zd`ta~QSX@aHvrqKcte7F$SR&uu3yCu#FsK##rFW)tI*q34_$@cV;xkXy-@Q30faH> z9}9d7(4RN90-9I&vGc}Sfam)_=&}`jNg{osXD;(;HohDOZEnlq>+XGP!E zYE)jFs2*tw2p34N!1eXYQ%xtX_otgl)b8n33`mmow!#npu3nw0YOG#;!LR0MRa455 zmCkzgS>FIL>?6KQ#N^-Ct9LuFa*g=+1Qm}-zbNn#K%bT00W^*LSXMRw*e|*)n=4kX z#q?s8WaeddHo5ycv$AzRR<2?ldvI2sC03q7?H()p0*RH6s%!r)D>s(}9C{N-)vRR5 z55mf)vxrN4#K(l#;h50BW}=F)uYpu*MQz4X-I(;}V&xTpJ}ZXVnqoasZF-y~WBNc-7`f(k4#QyB+5N5xytL=OoUCC_F#Sq5jmnWD-MwE1}ud zKTDuo{l)iA;(g+SUQAU)`mPx%f4ACf*5Vhpl4Ubk{&Z|Eq>1?6JDjiQBJ-ZOUtn8x z62C#8{%y8rP7T=pF_1=Y6XJ0YabKQATp*F z>0hGKG3g%)91G|(e-xk@&W~mO5P;{;gk8&a;%ud7!xmU&BL!!WW#5BqH`ulQsW#tX z4GaT$x9wrx@w2VoQX)k2g9?;C^$Fg7^!1Kjod81Z9 zk8Dp$I|U~(e2tu}%l>DfdCPenls}aRf79js zn1y8c%y=5m{6eKv@e=^_&Mq~*AcHFXdXSy)vbA9B5YMa)vTyUUy(NO)L}ixBG zy=-rJplrK5I3vhD9RM*bHBkUYjvnq|-eZa5-%0Zrw(A2q+hDHYd0rP9McnR0UWWXcSXZ4l)_Rcz0#w4V$d zPa1^U`Ts`c`zHe}6BUIqgGjYJzQoJ6M*?lOJr39>$S(G>?QuX}I-^M50vjYr-vGRX zWs?7z*Rn?0k|nYhUeu-7OJLsiF&vv1 ze7lK5AC2ddlW1}p&g5#l*+@Y53+5LoSi z#0hqL5B`)e*m^y9DKCAsqk9sK=DTZ5A(hJL$sivk0zLvOUlu$`uY5{zaqJ7(#BKjv z<8$7*zy;B5B$97c2e8^isD+6@1vvSo^d+JCi~qcM>(f8V>AT6@>Tck~dj9ciXS9Us zo6kT|XKXm-%3Jr%*ek3Vn}z0#;GoWkU5JD3kk0z=JrW{#d<5nyxN}=;%=ITjFp}-A zd-jgiH{Idx+WF?u(>I>We(@_q{B6~g z&n@;=A6j73;C?^7*Mm0A+~a-y`T+o}-bf8T5o*pS!Uv;15u}O;&BTDE?Wz6R!Se*N z-UQ(sFEyvyT%08eap0l3F=qO&L{bEjN_ekXk5d$s&aaC0bNZr}W?!LJ37Bq)Mi(fQ zs2>HA+p>7UV0L#)U}{<01n15sJwn2>7ODhKR*Xj?Mr*nsY+{7Th;B58;%mLJcGCE-|rB=ESVs_>sca=oo=de zdQJca<7zWP6;_Gd(SC)oVk)#FL$xB(zGP8AlE?iA=d}#Z57EHYjf0O?{nb4FePruk z%}$}#!J4(W=zXRkcWShCa7n$O2l}-RE(IW~zjql>Z|^E}*D{zLD5{!9RRgK|{>H(B zx3_h1zOfzG0AjTgZA?U;1y%hJZ92ufn0wRj1N$99^^G@=wd8pNs@JsTtI1Q9c1N_puW|?j z!je#)sMfAsRNL4{Yh_-&pdIXCROYu-7Sk2`mFnWXqi7ml;kO+hJ<}DkD&+5%G$dza10M`96^7>~at~ z!|gPl(UHay^Yd)^-}>cO;N0xr&q+b~MD(XL-(H3H{I0JKY)O%6btDn}g~hXC>^((m zMLQAIEUEHOa+R=YP^ewWh@#X-xvEHVP(qe!LOv|vkNfFTHBn+!?TrF>P$^YQ1BnNZ zTRvM(!C=2?xa+DsZ(WM@gNpx&joLPp96iRO7Y%Hi|WoL zWh^PlfqZ*0$NAcK7-4;jIPn-jGRIf$nA}ls?zEEDKGnoqBTO&VwSTM%rN-tlD*5py zy|1W*)PC}6Nh)R1(tUq>pRm(H${SrO)pVdrK^#r$BY@zJwXw9S=r}zlRZRt>dGo*v zd^Q)SY?L|i5<5rnct&F#uRC&E-SzCe{+(!p_4xJy*qvA3yDyq9@7`m**DirKsWZ(7 zbEa1Eny>?DaWZ7sw><4P{*O)aE3=5o?p9X>`>J8VXtB#5fCUUdx}m-GYE$Sp+H8LOMh>oG zOwW4fCnp!nd%T&i_a(;`I*Gqv3C&*!I#|BE4Zu6-aVuAop8kuDi@$5r%2wHj;jGK$ zG`PHKEBzCG4noN`D8Uh_mgB|&5Ou{5@(7KT zjEYNr3GOcmZa?S7z)Qrt%&|H-5;pb=UXw?48_B9R6&1dCyX$a4Be4gQIuz8JDu_Jn zq;BHv($hNo>+b928+ad%m&f}{TXXAo>;0v#I8}SgH}L+_i|Tjl{iWW0?CfFXukilT z3-$iem-&Ek`73N5SACp%FsFRIQ?;*tx87fRRZjT^XUJ}f|579I6nOI8Ihgq4k8tB* zAl~>i?@$;w-ifeg$j2?8<{bxnf;)4w!%(Cj2W;n7$s_GNAKvc8?#FWmMFQjp)dQ~) zzW z=ntlOlyoHVHE=?8d6`}TY$F*OQ*$j9TZyazNcQIlhx6U0`j2xG8)N6xz|8`?waW== zZ)^8t8_0>6-YQ1W>jbVoiN^rCyMhPnEU`7Cc#!ENyI_s)>x$z2l_oK9#=E@7C3ZTS zm337!+P->*zU0;m#?H7THl^;ix>E1T+d^$vbVl1Wi?N$RiznPNX{xjEs_?mo#Xq|3 z@Mb6R7O9M*>Oas3NEZsV;P+;V{f1Kt6WD}yE;V@Q3pzip{He{vfOaIKX^34ZjCBy^f!&+OP}Ix3D_E5lu*|l+mKHMp-d5K zUCI}ag9JA(TBm29`@yPwb2>Cm>?P0TY#{Y`6lVl=Dx>h?A$m%?$C2Lkn8Ufc_R|f= z=|Oc~jgj9Y$L1Ybt*6!_X3G%)_uc|hU0=|tH0>ngID3l?0p@pE#AkiPun-GD%)TX| z)y@(r&Lp<`diHb3c74OQOwKyUalfBXd+;?{&0}Ub_1C0l3cN^-TDND2ZIlzo5+x@w zTKf>eEB~I=NnHt@$p4$dHqBa}+c*8&v9`))$N9bP{y3)rU}%2FS;A>pznfuxmmjl& zd|$1e2Q>5fv9aWNDj1HcsX9?k;3j8f@Y-na{bM)(gZGbB>HTBJ1n-MJul@aFncfF^ zS@iEZye#^ujQlApALdku@Lrnn1t8IKD}x~kxY(C~QeOfFWF?@(OEVCIV@iC)VtWn5 z;iba^ad;I-6`#zpGb;`XoZ7Xg0P{o^@z*}$-y!W_;&2F2IHQv&_ycPXue5*N=3=VW z*KN*IV^$25!keK2G0>;8vSN__vs{|)VE6;q70~R7_ye~SKqHQN&Z7sCQeR=QO1~jR ze<-ao8DO9VKxIws%YWiul`Qj0e4k)AobwGD4R`Vh76cB?b~0|(5X`l~eb9H2;?Mv6 z^h6>_fu9Tb8M%!W;IE;55tZ^T=zS@p-(RoXHCeo$n)wo-qfPD2x@7po% zCiBX#?R+F{iQ~N|zKJ7^v5Rs~WJ`WbU?KHJ-5nSweIa;q^PAzE%{*P9U{gZ_cELSH4!uLFke+K20+bZ|MK{J#^88sK6G5j zdfOjLr=gDEVy5=#9O_(=+2Y4wFC>~-cYbCZWy2klqM1vTG%v?5?((K_q2^`EW~*Xg z-s*BL8yzRbA0S|yF$=r-q|67%)~`w$Rv}0F5~Uc;ap_qg;QdUNasC=G260j-Hk#LY zPR^Zj1rAX7fUU?_GZtEA(Bw-C+j!IV&nU z@f~v#5gEWukR;h-TGa)~5`lkhm)QLlYsgl~Ctj`u$v!GO*9tLX(T`luXZ2*2n&igz zw}Sd&a(Fm7GLo8d*v`$T?C8QGFH#M8{c5gm8=1#=*M0)JnbnnUc%ls3V(A5Zrx9_q za`>F};>}1!C$eiP`Ebu_G+O|5?$whbnG?HH0Pnbc>RX#C=EQ0_KvQ!We zCHS}d5#cqiMT2?-QS+wR_iFz$V*G`|n1BZ3gyEn)w0RtmICuj)KLo$J&Rb5at;D*B z(GVGsgG(5$n4NoK5LbyV(iFn#*T^%YU*6mv#KU0v{St;5*WHHkrrGk_?0GBmNEe#5 zVZ6tI)NC#lcZxW)hw-Z26}IfpD9f_wQXvus`xnEWx}G2215TT#du? zoJF&29FjO*kB4X35M|?V#~qvR?yjx3A-uis$@}UM!n+(HW5_jJen%zV$bwSvl(i7W zddqNDnZG7LLT+D|UwVkc&rij=ZYVt?xhB7LsGRnYdqL}Tq&OB=w*^Y`CvW zscat~HN0AOudGQ#BT{N}o~TMhp93u8f96E*SuyvME7}p+y>JtOTpk=A;S79T&*Kw1~)Ay6}31s$8PX@~zBMjM=lRM_J zE+mt|3j3m!z6L+Zb_B%;t3$rVG8J1pXMMZ;@)mU~Ha6appL}pTZwfEUT@L=WYHxnH6Rv$7#O`&kQ5U#^qKv)T7}s-pd>eUB&Jvdd}s z5}a25laVBgMY0@U#^EZR{F}$H=y073$H$d8fr6 z%u&VB-X+6sk-;>XN zQ-0lvnTsdPHkrLXrwss@4CQ2A>m+ZtSMq^yP1|v5{UKU^GyYKI@F{D|5)g4lGplKb zP|2J+Q}DyA@;~aI<1Ad8^RhY0%a(RN zE9cVW*aYVY%H(Gg-@8Qx-Ax|KR(sdsjM*P;f|;<}HdT-<_Ka7|vo0xbF}CCbjMcMe zg2O~Efv+w7Oh=pgF14G?&JlOCjej!BJ%oS_eiv&elMbtdSZxZjbXJ1t(Rs5+Rv}BbXH{>t*;lLH@Rjx@Hc=*m-Q6HD zAf^F_vQ1{gJa`qxz^BSdOdvV^6@GZD$!H2ujUu16Zu9X?HbJp3FX2rufxS?loOL#V z3Dt7MpHR`(?QKWYjkeAG_YzN?;7IK9pxt(EjQi9Yi2M#5@%V(pBF0&cj4_9EmL3b{ z96^6rhehh1q4H_wcUQLeCH9-l!XP*8`R?+ZwH8Pyj+f+h zPm4zP%AG)!VPP=&1UME<{I@MVx>xwnD6@Q&Zu_G!@u@LeQA4f9{T5J>wvEu3J?S@y zRdl@lq+d9y9>A*t^^2=m7M%1eN;h-kpYPM!VZZZzdC@Z|(0;z}9kdArI`DYk8Zr_X z(H5@yx)Q8f)o_FJcUF1{NbT+r@kwN&fYn7;Vap%vX)$@LDv0>y zMNxaakACA1lXvg+1T*!N*J*%-ng@Xm)espYU4^D08m~TqpcO@X% zCn(!3$c-=8M>MHiiWFCTG`*Ka*$NhsAt!MOym+4f*{^&TyUnR^rF4b>ad@UH z=ls9OPhOCklBbstJ)xV+q=kATW+ZtTU-5Dhdf1fPf>hXXcL#e)C$@87!tDRS_id)r z7q*!G&-9~|P0Mjh!uP_I zg0eOye4nZ8%dY}gktB^maaj{QDg-LY6u298Zm(!C`S>pwLpN^_FyzBu%$tUQz)1K) z%JF7-MV3Bt*E@>{Q|D4c!G!ppQyw~l-^1OtF{E}^vAe@*_#Vqv-H73dly7w6dd$e( z$C>V#TYF<|!$Tg9U5vw}K1j8Pk)dkaPHC_91k57U)@G~y zhN?YEsk_`Y9K_k+G-y#shYFJg(F7W?i`#S$brR+>GvqbA?5^hY4=IRQq&OA64&FNP zA<#y#BBAI34R|KJJNC!Zv&+IuccE3Y^*vv3-vK~L8=4d>ml)Dq7)t~3?oQRIadUSh zd0{_`ZKjC5`Kj>Gsol|+d>*G=pE=*%%$`f^!Tk7}+xxDgBfh^w$bIN`e!9h5isP-@ z>prxVQHpamJyYT839X@Vp-WShxpjY6lQ|o+eXl#AY>V~GtV{(m%`578eNxIllU;xh zyN8p~@fX77v1qhzqWke+zV2LX*_CJ4B?y`S1i{9gZ{XR@wu62otxxLS2(-0{-+ ze2;owie`jObH)=k8~Xh#H=67DdRr)6>oriC?(K22gsKua8lm#N-lJ_i*}Y4Q8p@~4 zaiOUaEA<5tqY%uS}JDOH+`M>#-tpeW)lm#uCN zT6!@(oMAUP0ebh2=-2`eeKF|n&W=JfbE)21g>djnYN$!YZp?RA|FiKdN85rijTLD` z&kDv~-=FR{zBG+g(?AIfDeN*XTBIRZiD#5v1S3uLyMF(!1<3X7{ou1h{&_>;vf&U5sO5fI{^9+>H}j!-IOmslkDH3q;YX+9PblneV54!XF3F zPz=-g?wv?cDYiEc#3+wZY=a#8yi6y7PUYP*)E(Uz!B9uV9dwOFt2eo_S`9Y_j7Wch zre-4>Nn8aT@-33hD_=Wx1XRABv%#4;hvU1c@OjK^gLb)lbN1?a8D8!4Rg%T%IZC4Txet9zia z&`EDA3DB3s(T2$wiaqdo@Khe}aCc_TVg6q4u1igSAV2>0_P!svAJ1ldhRHr07H=(% zuaJFEX(rI}9kLIT_ZuH~pDa$!E08DR_VFDhci%Af-&uyzb$8SLov$ZDou9_l_FR!q zU&pq+68`z!nBr0)=L;4JqW z^w68-c5x25^DNioe8*07TgefH>$z&8J5q*u2(4!)x>|115t5NSg;YYxaC9jSt@m(W z-|fx2a&K??^;1thRp|`b=wjxadRN_TavEoFjrHbHgHPV}Mf7t3djNR17eqL*I+un1 zV!0~nChRZ3>4D4g^9tOE_7bWEPeznuP%w=?g?P?G1&&P(WkPZif96IK$=*JfLf&O1 zE!aS6Do<`k*kx27&P`3pNie%;pfP*#yvoDTP)&GA*zzHRY-b~ zb=1pWmqgW#^W)j!=n@T$I_z%lG&WVuTJBYwYu_d=J37i9nI0OP40{SPCkHmuioeDI zX49yxJN6Vb;}btLOd}(EDAg*M0dAo?dIslKW<*Gu<|OoxM0QXX>*h+i*M+=jT<-N_ zq=?uYdv?}Yc!CCG%d}qYCVRHacsPlB=s}Qu-+S=5elG(PjLP5AHRS6YSCOCOB&tZ! z&@^jko=e|#Jgi-V8&I_`H72JVpL>IRZB9wpVOcLNW?&vo=TWY``3bYjR;6=D^7*ZY z#?$ZfWBUKqm*0y34q|+U=&bv{6x#N!LTHQOcEOx*<@d?n-}tO66TjYH_7II_aG#-w5rj@xn>^M_&@fbc30##XRw* zZzvxJz#Q&NcO{FeivpcNSnCS0`JMcMgRY;(P=$4DMg#ckC)P`q+y(2U3xMHg3?B4L zd9x$?AfirxfUkKFmiuy{&b4{;O6g1Xe|WJVntgT=bvW3=i>xv9^dcPZ{P^Ms%xA~P z7w7(s-m}ehkjEE^+j-9~=6TOA;-;s5LYx;3jA02%2|!Qw``|yXC|YrZ)SYcy^p~1r zJA!vGnJ~Cf$}GE$PNR0K$A5b9J#u8v%pLz9dv60DRdGFj?qR(;&QNMhMf zgaUkXQ@Dy`8oOSB}=YZnGc4=ak!n4N?_1s3>rr@ARM8>AKNFFUSfqN%#4fVAqmrWS- zrh@i3@3$qF$cs4<$HocYUJ{xs3Bg-`!CBe4)OpbBD#tJA6>xUz%-WeV@0=OMS>*#n z2OR&Tsf=@ljQTtyY;YuM_fM+&qWxP=dF+?@;2*tnzyk>hAk)#oK^;);>Z?tT5%BO54qC*yvXMg`!VKGC1|C z9ln;&sfR z@V{sw3wXVZn|O-h=Xm*>Kqbr5-03V(?r>6Iir1)aA1gJnI!=gIhY ztoko|*Q)&jgYEJ`I<_pRV^T)zOkel5o7o$!(9KKXas4SN|-bT z)d3}ode;E~()}qG%+T-nS!UH&+Qag$43RWs2M_sY(VV_@l5z8oOm)iq7@I@0cDPIu znhsXXsLepJ@mUv@o+}$It&BTpuedsTqpO9!*EsZ(tWqkRCX0}07P7jyyI`&+xs`0z!$BLB~=ffW*Hq?l>i+~w1>(#8*c z%k6|$p}6R7oO4Dx0*=>EjZM}rB&D!LQTfHn*Knl6d>?Ja7t1r?@FBfQIHirR`sUEG zM3LAsiUUMkrVE|=1Xfa}c(^XMw6TbvroB|YiM(W-ftkQmeL%kxOjEik1*U1A_GngP z{-=FxtmE^+RE!8brgj%t&apBXqMq$Hin|6u(F@hwk1iAzggaVD$4b5wgCG_5q?ZMG z!JT4ecjSDA{*~5!p*nO1HAUuBuAH^NF-T+&`bi^04-sDw(d!NE*~T8)v-^6G)~Tm? za<%|Zmbh}70hd9E&62N4VCzl24r?zQa%<1FxO27+AjZx(*3fIC_Ciy4?b%Iza+(LQ zu8et-QZnq4W4RQtx|jAsqo$7fNCT0nIhzMisfzt5Kr*|OjmEAV%(X`bpoiKK92=;@kv%cDo9!!E~>)qN54Q{`~GXO(W&RSaKuz#lS>(!z? z+uBEafqMdEV!J(OT~58uAWtFEcqy<~6SbvM8#I54X8?e6R!I9O0^@wIt=bFQDSLAd zQ7Upa$w0e0XR}>Or6BFu&nS1FTk0y!kosLf}MWS$!Vyj>rhEQn}%aq&NS^6PVzw+ni~PtE>`yI`pIN9 zJ(2pJgC;aBKR2jsTMm7l_xs`1)3m$RgH&Z%1rqO@g3}PXKb6rH1!A-fxh67-T4iXJ zvKC2I99`LEw_ge(eVwGgkwV0nGb3~#iDi8puS6>ITzcIMr>bi z@j6gW8*kPs&jqx$wAdY(oT9z7+Pd4W>Jqjugw{*>7BJ-^WUjwE?5n{N9yx>`E7-+m;~1`NAW z6S@r{NA;)~wW3DK`2~l-5-tnKHtjhqE0pN_xia8h?BQv?d$}@bR*E--r=aol25UO> zB!Tco#;!M|N}zK-5uj5fs-SZ&uaJEaeN{+JprShy14XHJJk}r~slve~>8w*^2n-jr zX_JCaqS_*+Jv@-T+P+r+EI@pF%YmWV;A@F^-v^BLN~8ADszAzHQWg7W=5?}Yv2XJl zn6SnXSZD_4M68iyM%ze&gpMCu;r+=Vax%G}m)zz9xj&`u)FNBIhPL^r5F(U+Bj0YR z>!Xx3k)ERK8CP+egx*5)w5l_ud;0SO_6wPz!IxT~~R;Obt!V2W?&7n$qq z>t-e(RK1=eW6*jqX>J6{BM9Yt<>Vb^!^YXXE8BHn`S6OT96;u``;4UMw?y;XLiJ6K zkgFzS>|SSO{eS>jUyOZTB%d3jpI~kgkniAa2S#S>oIO~ra z3r}n7G3*__hR?lir^4M%CN-xi5#Z{pIu%Y$3C0o^RjEf3j&5Z(F5$&gTn7;n#q|-E za-o4v6xY4X%;{k1H~EL+^a2m#p+9z}zKXFHv6ZdVS9Q>7y;0W`RbLwkNud`|Xea7x zsinSp#ZvH~g!Y-Dtz(k>w7@^+Ujc>Wr_Ogg2;DBbAOi+ zufD2yrJ$boGst9H<2bZ@Qle7LKx==q<^fXLEjN({wE>!QUZZ)P9w_EHIe%&HXCavT?n^fkpzxuvo$| zR>D^$VIHbYY_stS*{>q3$1z38Y$e)CuzF~BBU$pQMay1RUOE0q*{ZoeGt$MpXT!XB zeX_7~Hg(nJJwx_@sT`xu3g<_yQsB0YNyW0P?Dt+yoq;#kysRU!w1` z?v;#*gvQG-QDJrTPw?b%+o|0=xNhF*@WV>wn0!BD4y?`%j6IEkYb<`rBXIJsto!^e zNCX_fb(5nfHcU~waL+}`Otub6`jpLpCbT=jl7)2RVEHH0@1hqj)Q3glcn3T~!mT-WTkV&pdLT!{WBAzs~ zrERqF$(vEy_%|VHTNP#rRqekpmxElFT4ub-j674Kov@U|N@y)1;BRko`14<*4$a`u zWqd-d+MMXMN}(EYq}P1Q2^lj0FfsHR!9nl9W8R*)S`ND4E}8QE{o|O094$u(s`TsH z5KPKD+J?~cG2}^XPC1YT)X-J3VEfSv+{JtaNM#l#2uLxU62zEwsiL zxq=TovkvLap*OaY_s^2o9Fm?R7?mSKujn|1eN$FWl^=V90utvY{Rb66S&laU>qk`_ z=U!+_H`v#+=cpo@$f;bksSS}cqLFUPZ=1NuDh?`kwkd_F1O@fPIzmqns1Y(UJ$Ex= zFv1S%0C;_i=>y(VeTnnC77$UtYcUX_^l*;cK86vrgjhh0NpRO^9WAc@|uA{Aj zQdg^jDFh`uIi-v?Z|#zFKvmkRrDZ8`a0FZT zvSbl(#!j>f(2K>#S#}#SlfyUEqyf=nq!sv*P){M&({!YEXBi=YbRVsdcktbOlugR$7 zDw)joVWWW))ahH{Q=wl}$#F|qS!QQ^tG(>IxSl23*v#Yb9I!dcPVGcT8~(OyWfi{Wsr#?AX~S0Qjq|%mB7~cxII@lvhn~iK zK>1qcXjLys`fWnJ}V-^!G!D=0e=geKZSrlUrE2WH8~5wN=M5z`=M zrmUG_ry1u^LP_f&2?HWoh^sU4%#K&>EagDlG5&Vt*E8RJt=$k}6SPLup zCpq9U|2U`rqQK;cOxVS65J)Ku6s`MQPSay6D~{?+ZvE`=9{IVyw8dZi1?i?#t)enw zAY?bGR>`4ZB7&w6$9ws`DK>pOnpGCunFle5_h$i+3-RhH@{Crwio8F#R*>S36sLnK zLMa6tKz|;PAgz>wHB8^&bEKp^MV`siW1ZNMA@38fg^W^)C2OQuT206$K)5g5RzD)e z9xcEVS3o@w$(D!v>3adpMgUDwhl+J5jEV|8Cn%8PpWW)DH?e$CuzybB)lzm|ab%NO zY_=5pdlR(qX-d)8sDJUbaiwWpI`z@|!*z>C|ew!UAWcNOSRu0T)w8qh85v@1dU z-lIs#h(pR>XcPq(9S<3N7i1|!fE+=JLWU}*119Q#ht4+AJe{`4II`R_Q5+_~hXV6M zp7CMy!!81}x)_P&_zvT+aF4jvxjay8xZa*9OLvO_96lGONDco`enTsW@Xufu zGmDzrW68uXxMnm+s>N<@E15#bIAe+>ILMb97NM<{-h}4&>v(`Cs0x?$4PPXmRDxV) zic+Dr&uY$JWL!vX)ttY7TzkBpJflzaLvI3d^09cZ_#SYv&gP%(G!6ug4 zh~D@)aFqwclLUmzC-QaDMEa|s*swEYhg?H;QkLJHV)Ne22{!sik(`P8T8j%ETw%x| zTO{yz^^cR&S}aC17m45p@F|Swwir?L3Jf}))QPg|)?&g`y;bppu;GvtyZ#a7SfvOn za$pLD%#9X8?gBV5mBK7WY*!d^LyX973Qe<436X^lDKsNisJM9pQ+AT530`3rQ9~ej})IyJ-}v@S+Lzo)}c* zK$Ea}Cs;WKMkdOx-|XU8-N8uZ9x$b(az-c%&@?rd2NPJ5%9hV$0>==(LKG= z2MYH_AcLv%4_yAM+yk$2l}*vt;9>kP=@dtDt>|W^hjH`4U0QiPujW4bR|xrsu@l34 ztE{>pKaCGDC&-Ta0x@YgUlW@3UJqb>s8gRM;KPajp|YttS3H7J8uj}c@j7c{v}2%= z12~Er<=pvn_WY+rhPq*p`ou2b(Xf;w+~1fl&;|0t1kiK-5Zy2OSB4p_J3t(s@OMa@ z#^s=TVmQT&Niv}Wi~s|sK`Soh1u;22l z(gVH`XZ>H}*UkU0@#}F)IwrsV%X4!0^^%w4_;oTbCzW3})mr?zgFrmL9y#ZP_;r)Y zX57gB*EW9ryNZnC*J8%k1;bL~hu9*9*pW>By0#6o<<>CERD=CL)K3kwm#K4+6sB}@s9g*k8EU1N4ukD|9w!A=M+*W#5uPq09AenBOH;-qQk9OmY~QS87adWaNCv9u~G2@ZaQ!eP+)XuR0FOFcaub` z5f`}Dko%3`J{bpB4Y?-V2cmFkW7kgcV&X*MLU1Jbk@LaSpo{vebRQEhm7jnwHHKt| za)3?J0v5@9Im712n|}1j+e2kEe!>2nHcmGB%9}Y~kK^aB(3fww7#WB*(`nmC1xK)58x~oZCTN4#6sl z7BfQqfh_#T!{pqIz_3n>*ag?hDQRjqEV;6z0G_6{e8ZVXbM1*}%P%d78-2I((izvD zkhXlCKo@y%vx3~lwdJhaoq)D{oyunHS`yE*xhnF_H&2jX|M7Q=UtcBAkzf1CWSoKb zTYkme395zujyYYAA8uuS`#*qR<#fDbO>a*cze)iye*N3&Cx>6VE{fyV^LaU`{Q9TA zS^WBU0`b~%Q|bxv>z`CMe#@`_EBV#-lm9ZmN&zu`9oPHh@arF+kK@oWx6_2If6C&aJ6QrV2NpNr>LuZlcj ze*O3FEq*;hpd-KD#Ju}ke*Is`ujNnwm-$r+i1F*E-A)d_j(9qbUvJ~(q>itlr!0Q` zj6giUx~cvN=)<98$!5&NjM`=+Ua2Bam|urcHKh-aRdsAXe_hO+_FI1aU&*hfzW*}6 zN&zu`9o+Tg@ayBhjpNr^UQQ~%<_mNgU$0b<+w|cZlTL_V^HnxuIZm`~{Ff{rV*Ht1i%mUmqn9 zKfW$XI3a%3RW_sdujBdk78Ti!UyJ=?QpII)Sem$0;IfDlFwTp3E*6O&qPSMHxmC3D zUflFci+gtv5blllUL49ST)6lDd)|xUPtobh;Qv3p7g?juJHF>)yXDx@5yxiV&b=4q z!Vw$@J1gU;^;jt(#=mPL>`M5Ly%!6O^L}CSukxb!9xo?#yj}Hki+|S>i09wtBeI!= zW$JI{y|_wcGp^wfskZUFRpa*j>wETF{yh%m#opqjGv>`ch703YZXFmc&;|0f3UV9s&f*%i6Bv9) zt8B(Q55+U@c`8ylFt+1gSa(dCwd)7{S(Ii@Dv9dQo$Jq!2P_tTiaTTu0^Uc$|+mdf3Ij3`%|KgW&z*UAesgBJ$ z#RqpyUnNmmCYzt-C)>WRiyRYPu}%*BCgn)#HWJ{YCl(uSg&D1KHl=XQX7(hx3C?as zzDlI_QjzCk&VefOT*~>Bt7t;0G$GoQk#cC@ceIz9mp#Ts5!1Arf*kzE33D82nGP1@ zG(ir?o6Ct#b(HK9tQ1c&{!o=@TP97a={NS~BjhMOg`>l*mR((J*k~E0Nfx(4WTol% zE(KSb*Bh8YwVR)yD$u41q~zvk%#LWFDH^DY1{OsFPelU{M+4<%K&omaP*V;gTWGar zZlkYd;9Y&$m69^OPI?JEWDO*JV4>4CbNo>0vao|{XVJ5+n!Z)xt1B92a6*{6HOIw0 zJ+1n7b+_lkR5oXQW>9Yq?nu^_>hEnUvUL$Z^J%^#-L(TC0h3D7gavGh#P*Ls>Z{|YPSLpdkacc?(~Wf9Y#%T_r*RXQ^8 z-~(6NZG$4uK>9vzq?d#70eo;X=Ze$^5BII8KREvV=h)_zQL*7SE7NTiM?U>y{Z$p) zF4KpQVHd!ruX1xF>+S0c61jKTzM8EKrEJs9D2Co1;igH3CAQtljnDVJ%rS-$u2re` z1tS$3BW$?Of8%AjZRqs<+B|$GY_ch+|0}9q!JR#lTfsG5b$h1TjxS?gxgM*zD_gNjt^IOdJ_t+A;UP~o zE#fIhkH~wTJY_RTt$RH;T1QzJBQfE0K&F$(lRjvO9G+P&X@L!=R1y@>#l}NdsDouH zITs7*B?lwOt;2cw&ED&@c|RjY-4ac$jAttnZ4;H2vzky8oBE2dC+EP+0t^uyy$Hf( z+(jC?QJwEqN*MHIOXI+ME``Yc?o!D_A9xAN<{E1w8#TX=6mmpTIgtdp%DtCaXHsTc zsL7QayekxtmXm|j!5{>MVXcNfMsSC0cC1uK5Oegk1fSqxot*s?LHS3ispL^xcIwUs zUvhHgFgcQ^ND7jj<3g6Dk>WI=$QKm~!A&ZMZB+{Z4sM%Q4u(Vxe1Jf7D|gL5Kjd%- z7b%SOSIYQM^E0}t_FRbwnh{@aBtrIF8jDnfk$k0`*knu*Fj)2JVClzF$8M#ev?Gdo z>MWI&6LHfypRybj-F%1)V|eq43qG(T{RG zO?vfc&z=5-X4&Qep46m~v^<`YEK5yt<_LdBmG|-?HhW`ahu@@Y@<|dviKLNhH{@9i z{Y;R#8{nP(Q~g5NsTL=x{hqw1lTG-hn7kRhX9?j?GG<&JlMrunoO!J5VEN9Z@>Yj8 zn^XI}U3@K^Lj^B9B|x|h;6=q`#+~=W61)g^3TNEPJ5@@xI>h)}1{)qtWNt>Dn+OyX z#PHO<7VBgcY;o?nr3L8a%{JdkoO#)pz> z^8iR~X|Q^s=W+SqsCrva3Pd^Vkc>~vYzrv}UB9LztC`Ay{XCOfxX@RCh@I@)t6m$Mk7tNM$G`EDaD2KeQ&>0`j_|A(d(2fVUL_jpUy)-sD78YNrpEs*6;7iY4-bSUtjXfBL}x= zqt^uY_6-zW+W%F(y1F_%hF%xqr`a#c@%R>j-DV5v5K!C4$K|70YXwQ=_7}SJpulVl z8o;#mr+GpvdgH-8_SJe*@KAR?*q5X9G7cZyt8cQerGryYb|PKmOM~8Wa4!T_O(y#x z$)KW_-@d#68dIPqC_3=UK1n!X4*lZHY(TEc7<8ZP?to$49Qj)g2uo5*tT3%VnY*P- zI!fK~j)^fe0Kss&8>!9HBk!T-=`#A>1rq+|R!DHwbZaWIRpixyn`KUdq4j7-RvN#R zMqT#KIbk}@kH6eKGbsHed@HrJDA_AJ$Kc&$N=50{Dz(YuJ@kasBGwz-zs&7ZvV8~IilSNEK%=E^e zdFYwt!H_$1wf55bb@WhhPeQ;YccIxbSKF6sFD(x^UQYv2p%&00H2ky)&Q&Y4;&=MF zTdh%F6Z}-QwhO(aP>@+qH}0Z>B4<&9?Vkx@JZ4h*1S)d|bg_tU`qqkF!c!H845iQx z63W~d+zU%BRV-DoKlrJ`x4SRr|4Qcv3O=K(alS}*ZGJyVyH(#DJlrF=$3fc=(kU-X zy?`k#Y?|JrT3FAa>6zRZdtlvOJFpW1DgOqPEpwxyTYEjkxr-j#BPYYV55BLj3x>K7 z_kM7XG$@errEqa(P%Oh}T^7R3nHi)K^oh7Ng7aGJ$WE;iq%FcFcW zZX}&Zhg;;m*qE6clK@+LDCU(eP)JZ{`zD+l7S3(Qwc!rz3Qtvr0I>mon3>5<6I@ogVWdjOol7uv^%p2Gt)Z5-V`(t=C4x&TV|8Jk-NW> zwE04D+DnZ&>z7H@aySMsP&g3%4{c6hZ$efIY<;!9R)52{tE;Cgs$gJdR}R$~`dKjS zs6BVoqn?WQRiRwAaB!FJa97ROtBcJxPT6^0%>^v&31Q+vJ)wr^Gt-xgorhum5qVav z(nhVTI3z)Bo}HYbCuxzCew~!w^{A2fE5<2%J!6l)CbRzF=FnC+QXn--@17`*#!wxF zqt|3)P>2Fe9t%zC{WZZ}XeO1H(w-)r)H~SPBXk(ON4hUSoa(;U;7J=Dy{=ZSa1DmK z%YeFWk8jr(0mn6ro5NZ|=U8yktXh9V?DwEZ6Z^+TygIuT>{mpw9~@*N{|JLZO$o!d zzPvKA9hq%$H%- zYv~a>pJD_h_efk%|6GSskD%3#dQnjHBH@Ux_O+p&7NjCojc-hn1r&Nbq+|w{38@}~ zB*o?z>JBMNJN_(PI~XJoX`_u5yM^>abEHXp{v$nCBY3gUbdFZhsu1*GA5!E*LgTE2 zb4hrDq|FqqK+FQU_CS%nYwgcGqaHoDCve}zT}93&sA+-OMTdfi`i8g%Pt~l46lij? z$;V&G#Iwl>1=otXb`(Uq=j$-AZx`LOV1d|eUrVCLHhk*aZTGFs4ULw1(CIfw_sw%uv!sQnIA z`^Mb-6-owbj|a8kE9`5f_B$NOEh7KAVO^GL7hi46`w7)<(AW7IZt<;GLcayMA1+`% z6gPoEZX|~QYx=>Ow{W<)DBfD7jM|V)X`xJ;qtjA! zjR~cdYQ8cVEkNnz)`TNj^pK9`rOB{ts4tKh?kbh-#GJNUrHE4C9U>Ypg`@ybW+Jfy z2v0d4@OM84_;MO|q5xJ(?J6t)(RmwHimymifjk}dSAgn()by(l51 zmM!#WLVBZ`hY7~j9IVuwgDJAKwB6DuMRYXB$Q&$+^U(8q6fjYrHPCNk}#EG(xpV%Sep@G2G@d8n#$!&1({i)oUSH ziJRa%m%Bi$2=>tN59L*Gn$e%qSITbT*62vR6#_CL}r zMtkmN3g~EhNNoyH_1HEKO56ogX$tyozjg{r$w)|Ig1cWZ)eonWC<`^pW~Q%gE<`4 zHUpWLxXW((u8l5}#0r2U9m`}jOabuab8$nkMsjLJ>*QE}^F03vVgX@tWD~pKxx&>b zM#`?uaNwM+VHVtj5$;jb2-igF838WJt7vsI*33FbKa8e`bS%(!1;fc=pX2UA&O2vG z(D7Q@ChQFTj+>;KB!L+iwURz3)G*v3WUbG2HVk*E&{%iF@DvrwY#8ED)}gDN4MSXt zPp@+~3`vm?%L~KBVfj@4#&XL*s=qi5Y3pKD0b3luSeE(a){SDLte@D0CaZJPW4Mxl zy52x6Y?GA{j5UNQ3M=&o-_u4nBc+rXd!NpomdyvlTqPNAlg z_O-8LLDYlz3Y*sMnDLs)$&dn>7otK)Dhzu14i=i|Ebkl{6{xq|jG>PI<6W$TV)Bc( z63W5(1(v0(kZ_TNNtfXtm+GG+x7NFsK{Gtv@QgfDHkSAPm^cbfeZ-_)NPE`*cO!@p0K!Z0 zjBpD+7m8;PWyQwev!w9!XkQ3CQ+~)*kdyzZ76DTIO!)P+2S3zJgL-gpm)q+LG8@DS zfcMC{gCD}_X;^=-3}8=(emAUi*1+Qx>9NF`|K^kg(N71 zLJO#1uFx*eJZLIi$pBi!djanm)_WG=YzbRb>hL^q{xBg|M1WMjW_o?RL2~A(>Gko($eDAffz(j(NV}3P^}zvIN{LRj zn&abziIc2Z)`&qEak3K#siqnd1Hveh8f!vFK%$3)2i1t`(Ux5lkFS^A(md4B$GU|9 zspEKKA}fbrz_+rfqlD!Cpj06qMMOEKkd+H$l;|~Yk#WoEyJO|dd&Ula9iC#NivpN7 zbwzajzD3{+bNS0etceW5N9lptJz)}uX=@UdMGp=VR-By{3?+F6E6uKaXJ1!&`Kme= z(H$i268_O3#drOKA(vA4m#xpLFV)R-1viZ_nMwy6VT6eui{*n#t91 z{lm^%r~iJpzSE!WKT8@NDRV^zTx~U*yEme(p6O5XbZE4reWRVnYjoxh%trU)X*D{H zpftLte3C|Ymk7w|s7BLRxrfOa$u^$mmKWbjXS{T3FYg2&-$>q#?HEEuyf@5CU%;6D(rkB5y!&Ln{Nt!tCF*wzABCn zGjQ$AgZoNt-W+6zd$YMHsHRE0<7}j)ZMt>py&B6AuL3ER}v&xgHtn^5&>zA#AjJ%*9O0S)oNw@hFQf6Wt9eU_bnts6gS1zCi|A$C1@) zr4w>X8-;9bQzrEtxJ+hlK-@T3#}-A}rQdIB7-o)M!<AYOkMOFH`=^6)3~o0x#I*MH>~a`0^16_~ZzC@%d$9596jQ zH81G~Y-gei=dAN~WmN1bDVGNB_!rKupE{<$^JCskOSFAc;J*Bby+O>JHm&?aerx`j zbbCIYwqLRcvrccSy=njHKa#4nYWCpt;9?w{GGC`Ep9Eg%DlgioFO}K7Nk5di94`l^ zHqMI3D{|@L{H1`KzFbPnY|xg@lk#;Gxt9J8&#be)XRYDPm$I#|nGF>Ul3Z`GKKkbK z5rb{;%f5y7{o2yMi>289hCQ6~$@Ig534KNIeaE0X2n8xHXRc}sxGvX*hm-$3$Y$d* z?rWR1N9!sMDTm%im*eM}zC1akEnTj!hHKSeT=Ot4!1|sN!KU%ARn|*bE>*6=(lm$XF9|a@u=rEIOd$K- zvmM-;*(iqH4ZSv!qbR6ZuclXR$RaK{N)P5JVZ}Dxk>F?+T9(}E4 z9RKWC#&N`*O%G)rmgy+W{=5`)`o3VKul;bZwP?$ujH(D;D=p!kIdxT^Xuqqouh3h3 zO=9bFAw{qh*lbrru9B~Kym7&`vXv&qT+Xg~TJ>k3N_15+kA0ol#;Dg7Ap@qVSEKf; zx~lcsua@U6*M3g(wOyb!BP*@7Ag!WTsl2YSwRgt^j z;WHojOd{XLNa$@ogSXovB?9d>+qK4gDK@AZA{AA~=0udol5WV-#b*LZtQkTMnaw&= z;a3$JBZ9`(Q&501>p^8vv;JkyJJ<%dzwADAJj>^+#5h zABvQvCnDM;CFZFAPrgxw49Rh&`Ar2jnMJuaL6mLUay-<#u~ z>C`ttfT@d;w52^0(*mF0g!9hU$S(gK4*ygXKmQ~b{ap1P6Wq|>`SEiP9PO%Fg>Xa@ z(<XoNgBBNpzlx@&1Xk5meD(B zy_7CX)PXy>7*x{-((PK`A}+{YiLIslgTDFy_7-VN$Ju=i3Hq15orATd?-p#ot04cN zcaXO9vJu36G&s03DS2N(^UOTFBOUn%@#v#@+S0D_b;sb)^SnKrvr0dx^3Glvd_O6i zq94pTVC9?D7<^x@zH}sSLE#GwIp}NYqRkVhN?&U_E1(1_TFd!mEs4fdv<}WDJpX** zKs?|;UHRMnhoR>uX}%u#n&uo(n!fhj5%t`#`#>ReD8OqR>AjstGXly**s(GW%FM4uvIPJbSA<1d%fRXFwB;@RsU2E z+xM8Lt6mMEfpatA%x$5;q?B&aS5tzR6`XWsWi`d*uVd^@rvp7+xAx$}SpUikPI*mx z=s`ksta&eGHXSXCCMlUs-ro7^vxUw3ird7mW6tJxw2O zuO6EyD*QxeNDu%aanWCOu4KB)p;f}OHlxQi)?TJl3FxoO^!^Is!&z49q-g5zN$Txa zTd55KX6nm`=e9C_ZHDb-;Q^Co7bRFUdp@5+NBF$p4*P`qGF zUeK(~%Oh?ey$Ka*l|NJoE`>Zp*Ar9>($)`EbLg#_iy1>}acZ=c6caK=;#;D)RtU3I z=t3Chp$4q_iV|0=z`0(pNflaEEm`Y_*}8_W|EC~g{KRXGK{u*Bk&6;l%gWBHkqOZT9N#CFr*~^y{6zm3d0ss0P5?v|o5u zG*bZ`m~3Yy46c{EOtXbvyvgMQA*NH2ZXy{1)2X~&Pep!dRoUya-YY7%&FQOS?Z%>sKwwkRMp~==zCrW7MIiJS$+pisQd>I49wPjzU)>aspnsyv3rI z%8#Uteaor8&1%(y=aVNY-L^oJQR(&;;r7z4i#Gov78Bq#kQozk8{Vf+UK5Dgu>IPw z?W#&q5tn9)xcyQU6b0#kss{iZF3s77|Et9}K3UX~B=Ajerz^QtQT;mcXY3*uoXUt& zNSCz8aPm7!QVU6XYsg1cat?_5)b6C<&faV-JBX42AO+xI0kBd)Pw=FP==+?kii#11 zq)Kfs26YxkOfmQXq0YtN6|r==gxZTiTj+EA_0SYn`3@x@L%bqG#wPgc6NNZ-iwrqm z$q=SJ`5V2LL#~ZD35_pRPIrBTpE({&9 zbTzZ5(34#)S=msA^}TAym;w7E6yU#<%z7DEQcyC>3lfn9-3U>mT>A=N>jnB5`b>wu zl^qzZH+a9ZB+=KJP5!j9af|JUk2LC&n*X}@>~O~7gt+(KVYSJL_RCf{Qj~F%Wm0G= z9KRqz-=L^tPM;`cJSE8xKA}bl%wVESn!l0pVTm;^0t2|_<1YEc*3SLAVo9u&5+NF$Zj zbs&u@7&z1ix>_XH-%4E;cVE(7<&>elE%uZpr>I!b>2kLHbJ5UYwh&7RQueZ~s&cYu zEe*0Nr%Ccf_R#;4+Bg<>Thc95U_ z>sc%bu{M+rPGR(@$6ey}5fEBt#V0T&UUg8b~NGaI(JxagH3rg{pLC@re zwV}zZD3hADg>R-|f{$I~A}9bhNwCXe#}Y@lh^SsmdWBY+QFK8#hp3*aMG4`l)QFT_ zmUNMbSdF?#jec)tp{uO+#Xg7m5~}0}FE^v#Ib|4dl%2*tu(IBMrH(kNC)=xw z6GA<4(3Y6;)JOG|p%=&*SD*Ev+hHYtng`AEh~4YbHz}?2`k2(s9*{O5%t$e32?{_?m$3x{lpyB8z1J;|}@E zjT@>FU7 zA-EQRKBc(K@!gAdg?w9QkZ+1+{>L#OFNuEa4tgxe2?XQo zEMv(r>wIxEvrKS1)>%e0)u6F$kcScMgnF*u{|%_8lX3hsz?BI7toV z7!8+n&j`{WxFiB!^c*Q{s-=T27`?0VeFMaNg%rloR=Q8+rm;AvD&nLcZNiIGcR zep`~!4OcneN^Fu?KeNOm7gMx!Z93g5Y>>fwy7Bf^V#`rMkx%$|EH6ypq2K^zPZV1l+$R3czrgpcmRmx8m}4OxMge19&Yw z-^GtlzVGKfg?AlQ@uwZiN+d1{qo);~#q(7Dz9j!2dCGR0zT}tp^Lh5O(na&dKS%T3 zP5MiDPU7!%(oW;Kou|Z0+CTBkow`qYCEf{;^;c#G1s|Ep?8S5pK7*yamb}Ty5@dP#8VYI4 za%_LWj|N58U-uGH>#vUiqy748B{A*SUqAhW5K*+M>IrpQ{(=I)T7Q+C*81x*#~YV6 z)pqgKmzYb)N39=Mb9#g{HL-oYqlFy1{@;z&|H5efUjR<~`aeNT`}$vWy!s0OtNxPH zsz2rD>uY=uIBnj`{UcnUYJh*7yExJdmh+ErD}RiB{s&x^Lq-6fHF3zO;%TkEGg{gF zNzIXw4$daO)VQ5j1pn#&q0Xv0*f>qA`V5BE-|}DOGS-ZR8&*wV zUV;~zG6LHcT;&ej>k_{sWdyt`g=M3vtKeAQy#?$Rkr^lp-=`WsvvSG!;mfmm9rZ)q zcE4u)W*pLV@Y6GH6id6^Tj*;^@b;@&_$e0D)1WS+e-Yz3t7t4k4XLV|-$N3Xo#DTX zg_)FzhDIZdUt+XXP5LCF*mnKN)zy74M!%J_$7 zNo!;&)qk&tof3hpLSIALf)Q>{QbCf}9rhTPMPcO<@HJ%nUT@MWtH_`&wNmZzJz&_p zz5KI3FuFw(ca?%?Hyf>&Th%*2!1q9lO82?3D;m9>fWNEMsjJ>WY-=(kb= zQRA<^qvi`-pKJ4JOG#rhoM80-(qeg$)rfO(R7->9KN#8G9gQ3^)AM{jm~p_A^5Qd@ zwWt?-z<^%6IfoAn14QPkNPK|woxE{VBmJ8A7a0X0u^lygzeg4N6Ui!{_H!nnB_HpR zsyxMU$4Zp=htPBS(3G;S4MR;oi6IOH^cTyG@}yhOHJ8oaTCSGOvMvVV`FtsvOvvbp z3a*AI6f+agJB<~OB-m;U>R6j2>CzWW_o(bSu1h3Wg6ML)fm{0 zB`1op(LdY~xzr?w;id}~oUAy$oADb~CnkowO?-oC;;jH zD=d+5J!wT`TqD^{AFeS3eRETUs_t?d`MfFix|BdiiDAkM7n7(ArbSs4=Fu@>(eY0I zh3qRc4qt|-PmxL`k{$VV1x?P@Sat4F%EmqvT+)AW(Ocu%Yzd5WF{ zymTBy43JZA>*8DKGM7hFz+J6$zp>+~qlN>^ztc&5Jmk-yKIzPrsgA>}&J3xJTdLzF zM0GN$P8!NtMyyK3E{jSc zqXwx`13|xqgf{fw)Oq>Gp@;A#QGDztbOQ8{5v|Zs2x)rqXoP%a|9n4IpC6uBeeyc3 zPhqF^>6|{_#0lMGm$wOlo^0tKp^X^L&_R$%TMvzlR^X@}dhNGIA;iQ-=jD%%l|SL@ zmOr`E@&|wO^4}Z~Yd@i{L7yd^mL9~Q+Cf{4`hOp%{ju^Ve7*ALbXxw9zpOO#xM2<5EEY*h)|Rpz+#$!@(kB_<|**`W$DOxKDz&A7Z>6~3zcH*=!pzfFj? zf6elr@3j0{wve`w!B>?(I#&J!vwY}cP=P3UXTe`AM+x5?)q6YaK!x6|*BL;tbzCw#r~H+Ne8kj~04W>gmMs%jnj zK#$KbHeO_@7~2VmHZHbty_c)PLKB(fYqnhz{q@D7P{u;uhu~lOn_M95-j+liT4D%1hDhau(!}Xak}S^>H~dt_XHU}=1F-NuM$bdjVRo@UOM#-<;HBNB!j4{Cow5v7XzMhDQpmbp446NVPmloZ++Q5yPaK`|nC&V?$l0b?0jluZ{i7Vs5j-o;5JYfrf+{Ljw#vyulKQo^|kjS_s!Q_cgI^K5+|pWl=?I*B=6 zs&BcAm?rrQOBEaA&J^2^gU{LGK!GNo$Fy6k8br?uf{nfXL0zVx?f@Za-*c?MP~$#t zqiyr-@g`5b{;3bl*DmkM>nomeil7-2g=faXSMEM5t@JW=sve#}{BT;{YohdWPvb!; zN1gWikW~(+7Gs`iD0IlwOJBjNodk>@#1h_WCEUc+LQf^3^D~5cq0^~1h!b4)G<`(! z0uGt39?{)6PjWe>85dc(J|$O!O(wR?Ym}3}->kJ>kL}~}p)yC+Di@N6Fv9g(x2LY*tad7J7CN@DC=A=?K4ct$&=$*ik?owq0q~y$J)X z--%TuaDz+ytK|5X@&^iS-m3sg@AFG$<<4{_!_2;F8>W#{@J<=w&b&$IRw982WdJ-s z3M%S`d$&qkBl5BqT-l5gGeWd`v!qEXqH0x~B@Y~mDKA%<@+KPZ7}5H*(b(HUV>1kk z#%jL6=2x2z%#yS)AI8T%RFlrMP_C35m&ZP2NxE%+jjB3I!!_+)3z7;Pv%3}~coVT# zVTC&0cE0P+wd`L!qxLU?C~3xEp33r7K)|$o$^4<>V>M!~@sCO~Ubzf}yoBTt`F=Kw zDSr2Y;pu@p#53{dD=Op-Bq!MkrR24CMfAl-NoAd^Dr1oeIXyF3j;Ji_e$A=yYy#^7pRppzjYHcaVKmPrk& z+(6OS7^hkltPr3gu3G54`r+yJ(5D3avHWIMn7tVBAfCTP{N)C&7A4n##F+xcATuGn z3B|@u95ErIJ)amD#pL6}BbdFXh@(pCW7uL1cM_6@52btrYXT!I!|Klq9ccf_=r$9K z9p`{@Hf4*dDWYPsar1qOg{CO3m_=)eonn0aQ6%ze7KkIU*zK!+Mgu}~_%R*Kx(oqX23om2v8MtmGLZ&1AE-7zcQlIweS*g2qc3JQ|EeXG2KJ zcDam@e1EHJbTmR?p-i+7C!SPNGkt~93SS`ws7ga0OQA9@!EaYRWK+vVsvY3f*(OqyBoT>KY|Q__ zk|xqJTGcaJK$`LK-3d176Xt)}UOB5|v>Hr`D!0*bg^G0ZXjB)g>{7FAR-?>D^`KD@ zbOwGq|A&#h9i?TGHq=wJ>=;yF%5eiU(LXkek+hdg*sv+V@fcm|kQzJ?LPd^s4n`KME~?n?MLCCg-6%i~if? zWH|CWjUxkMW8FAcnJgtZWUTumgJpEAa~OBeNw6(p7bKI9G_we;GJ_ICA-s_@o3yF| zDUZr=g0CP?*59;E9>6Lm5A|AZ`{l z(g{n5s_ik0Ote`Cl}*j^&&`gE&1L&dWvZZQggs~d!TtG%^@-WqgT0A?2v^v=owayX%YGSzS?r{P(#tB=$zgKx_*rnbq=R za5F4eOq49AT!=JGMY%sKoV27X&r^*TE;Uhd8ZhEf@*=Ghk}q9kkE4^S&7v1s6d~kN zmZEBEoO>5ai$thARJTkn^Y933)>o=i{iL|N)NvFN3CvU(`4-Yuina1cUMTaBCy0@` zDubk&^)}H;^CE@nClsLa_miff6DwawgVX7fID`LXGI`yBg$karY%i6RLZ|_ILNn9| zR(6I_hp>?D?++2vR{4w|K@h_^E3>VN6jL3BGZVM`;?G1v2Sgutk^~!*1kfb_LRU(I zULzBY!DkAEqZ+W)m*`iCCa2qvS*X`g>OH!xl zLz9DsyLxDM*=w2_In9S=^nk+>>^MXu&nh&Y1jfX0p`b;zJdNdWTJAPpe@`9fTXhN% zbX_j=k%c}~*^HO2e~isI_aY{Tw;NN*Z&|6XBWR4}$DG(tBqX(yof!Z?U3v8bv`_s8 zM{uXpw<+*lCMEXG6-b|xyxZ%x%GwVb}6LpLOw*as&gr{V&yA9QtODvSae_d zuc&(5w#JxHDT=Mq+YjtT1D-1AJClSc^3w69>isU$7b4UFW=+3W;WjP*HTBVW<_xa6 znBXY_GkMnGYN-jV=9_bKQ5y(8#zX!gf@!9hvbf3|8<6mi#R#?Ol8bX|tXo{3;*;by zF7w0O-D`@_$7>m$Y$c;bGk@gWsA`{1oH=#4z^ty)-20i;?V$I6KbEU)5r2w+yE68n zZ3@wH5z13$^aE2jhBTRWI72IO_^!E6^3^`0urh0r55s(&+I1inX5X3lY&Sh@rBl55Gk9@?;`CbzDT~Ktfq0$LOR{*)DO{R z;;JDMIYZw;nyhB>umya9n#DWCKi*z8MvMp;95di4cTOFO+d$qf8y{mYZ-2#FeT<>0 zSR%uHhApx;+waUpliL(_8y6B8nWbC|G6~A4-BsUdT!yM{9*@QLPz{SMCo8yf}uBhzlPmwb>l zjB6-TBUfWsR;nzY%`aO=#eG3WZLUTm4WCElZgZ40wX51OD&|}1hTq$Hb41(Q97)!y zoOHI%B+_Bb&V-3(B9esaTBP~51ss$!*CB3ILU-jTeBSevw7JSkwvJ@h0PDn-XciV4 zgVRg>@I1SJY>IzsDraUO(Pe~S)*}aVIK5fER;FiLh*i+O6cI(CzV^_JF2D)jhSLqm zE$eM;RLQ(I#uaGnqS9@4kW(1YT%sHLf30*wZ+G@l5fxPvUB)$cKqT;|P3_HaBCpj% z&uQe#2NPGv5RJdtY>7suCybkI^MM$zTi!x5qY2~8Sj+J5F_JgOdG$osAJlm`_se-s z{vV`pWY7plq%3uOq|6<;j1z;Pgp~3EPnWV&BeUI++2V~gej>{~k>U)#gyjq}wHA}4 zH%HBY(e<_X^LD-(eyVto6}KxP<+vprRsYmz{U;t>`&9o3Cx+be2bi$k?<_m*>f-T} zA`?f3`>6V}h{V`!3%ktd(e#1d(xExA`>VbtzD_rr5}BO>;w+Tq`BtWOin62HbNv3e z&}`9u&5iHl$0`2%Q}>I)C~oAk(aI@Wx1Q^XOv*hj7TZM7H!S}sdf-T{lSvOp!52#G z6nQk`KdpcI$%RkO5^V+_$(Xq93dwi0WV8yMS*jc`v>sr?sk`U+W= zt?Y)`0!1EluuZ2UzPUuc!G0>lI&&EHG6Gf>NNB8hV%#JT*vFr?@y9&O%vq{>;Z)(iu4 z?fDHe&w5xt=+9lO=ROkf4z}xu*b?UxMMr&|^}auA@xU329kaLUN5UIT9LQyG#YRuF zaaHGtZ?xVcI>mg1kW{dQx;6lYU*^oTOkdFfl|Ki}_+n!{8IHn8ePc;-$LfI-qHCXeYIAkzR8xt{7_(&llv*v z`97Tc6?EqB;dNZuet9*$Lqv`tL-|9p=;?N+u;CyfoONYBP3s zgF+qRKkz9bq0o;NBt;UKLeRKxQH6R($9HV$SyU~>qwl-TKHeBAy}r^Zbr zt;{g}l-uUV#mqd7H_}@*Iuz`hV!Jq*HMBOUUR1*zK zAV3HqphXCXfC^}o$rb9-jaoH=vm%$ajkuK1E~?#I)B#DKtjabPIK z<-s<=%s`V%v3Wp%ZA9qO-1VV80Q}n9Kvn39Go0NhpnRN~!xc`wh5fO6iG{E0v|^R` zF^T8m>pH<#!^BsBE5XLlfcza?HYP8~@_C4Qd@G_qRQM`cBHnIjg*P}QoA1MD1<1Nv zg||k35FXojvI7IlJm#{6ALUfN3&btjA49E|^SN1qg)SrxG?HsqlMRGWf)6Yw7exZc#)M5! z9N5A?sP!dN-^@N#fM`VfFi@XlnkX;UK}6}p8Gb|&{WbDF+Ql|{kQ}7By^cbCviJfdCIE(M(F{YVIb=o$q^Uof!sov<O0K$_oI)6$L>kMD88Sazgi1+;=}q5 z@F$`LTPZRwu+~|1+;=2A-5I*m#`@4FI$Y5|^2%;Me7BQ2CB=IU&0wVUJ-)w(MmYj& z9k1H~e6hji+->8t%)K<5ai(vchqnOwJLH8t<*=&Bywe zVjY$DAVA4H%-O(DBvyH;@iYXCQT@IUHIbCM(66$Gx*I?1E$30*Yx#1X`H>wWa&Q00 z5BP}j_fFOq{zd?shifv=NAy`j?Ji$uA^JsR3FFtrWeMw{X6XsCgoimkKyH{(ejjy| z8dt|N8X4%DK*Ks@SPx+e6Q$5rQ~Wwzy(P>igjM67}m}TzLb2uX5d}*vFiR(U{YM6}G zV>-i_LEd6Tm{%dohva!&cVI=LnqcS&moWrW4Y54W6ak}v;U3 zl@*}Xb*~_Y8`dMEznxr3- zrHwb&5}2L2tT~|5|Cm4<$pKndBR~fOYckXvH)`#w5%T@F2{MwwkjrC^^&YHJsLSVR z@aLifELO{f0Z&il&%N&{4lnX1{JF6rUgSdUr|dOx5+N*qg8G3-PlZId%1wqRIi9g& zZc7NN=HR+i`=mNoz73o9NljOO!x^o1YmpyGY|(0g<&S}-+L0+16X|lN^1zuy!S_X^ z%Xm<$DM7xi1wWv)l5rP`jN9X%kBGZpDCIv$xG#g{SEBC=1>7^7YH<<~cT)Ku;oRD# z?;>OWW(j=xtK^N|Eaj5R-}+yat0%89XNN!?B?_xgRoT!2gddE4J0wjw3w&YO#eVvC znY56Fb2FIp$Ek0wxmMqqTVEZy0N8_>0sy=EVUOK=moY7tWSiMl5bFU#G8p%euCIN0 z7wtlf)oBY)eUzwau;ad7a;s@@vz1#;f2`BXu8ad@OdipDjAY5G4vM;3SuY{0)cBEE zbGN-x^I9q{G|y1CvR5=KZy_X2d6%lx!KhF}rL-)8x8<1Bc%?n}U##5ENG7qz$f~NY zQ$m8rq(WaiSC8r1)EQfzklY&fuVwmpd-arLEPO(+?P$u0x&Lu;E8&7`qSsp2M0*-H z@s%mjo^$|r2|YST`x#ddW9eIE5Hx!5M`_D-C!pxHShMXojm*VV7fY$Y$2<(oG$Yj7 z3T$kKrXtRnP>eUOab!lUzdqqGrhU+vUt~!`a^dB^x>ofp6@`YDLpo*I3i6h_H#XN{ ze0D}a>boSg0M^@kjYPGmC@%+dGwFxJ*unc`brANtU1jsN9B)KSGW?t>s4F_Jl67lAv~}@V1d_{%2AjCXQqI!=DgxhwHJ@Jyi_-ClpKDqcC{OtutqsPzFo_s zOo#SkxWw^Aj;wE9`8-2vjOz}lxccv#9=g|IT+0_wknrBGH^HM98B0#ZC(!eRWCA@+ z*VSDbk^4xv;blRMjuBg;k8mE*mUoj_-&gvPs^g0~d$Y}t*)<9r2`ID7jVnEE>|!|v zvz1RGWV}I3pjdE%2e};o08uR2a#SZz^(~(6`0=fL+nB(L2L5+Uh|C9MLY#x{@DqX0 zTyfRG-layK>WTL%L}yKEi4aBeXsM|QwV%Zdj~gnB??YorTb$%PPlyHeQIj!)sdCak zz3Dtfs+TB^|H==^jN8}AOn*6ZY)(ZlF@aGox?g%H{c(rv`lj$0d)4~+b5O=*>pPgGTCDV`Zy3UA1cupsLz`2C2h^LP zLwur}aM8~2IEOJszJL~QN}(=XTrV@dSA6982<7pz0=okZY{YVexvf&vRy2}xI%&8yCT|dS6iOMgsohpV6 zt0#J%dDER8osojp1FHaxP&6rb@5)(%tG|KJg$B9_hK8mI_>Ww1QmOO2Pb2Uw)EcO_ zzaq)t*j5|vIlDMrJq~5 zelEGznn+;GmgF2+=^nR|LnO0bNfQR{Ty;34ecbRIBR4#o3k7I4eQHK?VWtX|dzuRe zNXVQT(D+NR9I*<`H55Mx8PC&mH8)JPlzdYSKoH5~*am!8Ed(<7X2fM?M2+-s(PL8MTA?>PlN}`=kkw<&EdHS!~?5Eavs2>+eiKVt^4PU3J+Bo z+PsBY?cgFw!zeK}-Ol=3kIWt%?xs>VcqeJKT~zA9;nCal6XeT1PHn1^xo?W)^d5Q} zmWZvA6-{C~-D+aU<#Sqa5}h&?5G=+Qx#!^N!mCn@oaV&2@!n7WBlakE=uP&!`n z6F^zngmny9e-E$ zja_k^o8K}gmCVt{1l}VB%J!(XFqNSQyL#qt)QVd4{r3IZvkh9F@o<338NDjp=DHwt?czwBByUCO=5mql05As2B6++ozomU#^w&Rsv*Shzy* z>_QM3FHty56N<$Urt`}%j!D_I`c8_vjVftwsS)InyI0OMFmG-X9}`&dsdjjb9bb_y z@ZCnB#^F(lBGt&oj5mppDkSkAg!SFv?V4mjXzJ}!3vhZ2pX)5+~vxOHp#7klM8VVT4niM{(Ev|m3`~!*GXU4HeI>8<8hIuEb zo%Sf%X+VVY4lY%Z_i_ZcpQw@SJU~xqgkI%rxfgeIkVmm16)%6xDu# zH;VRtRekQgTG}GhQ>%J|;Xj_@@b!^X8hASJQ4msj3fCJW^LRDu5Npltb)XkFKa>I} zl2I5-4^7U{=_nI98Kjr97I)3K96aSYLX*?*ke3PBP!2Ysn#H2kkxsS&L}tsJ0Jb?V z&_K~}&J*?y7vndWt>814(V-k93U5dkkn&mzeT{)LY_Rsw_|dlq_r5Mi#SWhi zb-`xz?OJdZ>Fudsgi`+K@LeOlNh*&GJ-0VoCyUSqSsf+lessnE4}B{&KXHfo37OGT zA?_FLUnGx|Wp*xTTw+51mx&`7*O=^JJ_&)(m7n>kJG5A-!Ixr4A`>DLzGrqudN8$_H}2g!}xql-yQia(KQ@r zh2QY_33B)lnPlQ{i$rp1?6w~hVpfOzLs@zXeEz|X^IL=namKPVrq*HUUfCm1zjpSJ94TWY0;iE z|7L0as&5gm`tZ&*gu;%rtevXt;3?eYn8&*lsuvZDiIS0D$ zz1P06wBmucvOR~5Jq*NMjb?&(VuJO#*D&{czV$h8?#oNz)LAYQ?bHRxSyf-RkSN&`?e*8oB3Ts;7Q~T57@_!LBkfj0-cBU(ZaW@(4 z+B@T*xWNA58)EfM>>rwQ^?ZO$ncRe}HN?!iP z8Ib$P35ZH-N5nxSnG}fP5PClvhtLi}KqyxrbO$hKhfvcLqc`uyZ^)n^^lJk8i77@t zv9bm2==Qsl;@xh?f|V809CqTi@@mF<+FyQSp8HpOPlM+mXI&)5p5 zDjXs6Kl_4Pv;Th0;s8Pe}e{TcVN zC6eu^HR+{nT!OHhI+)5_a^aNf zs6cM;q#2yi(G@h@p--p`u|8(Jo~$hrBJpxHC1e`p$b`5?n5r<*v_E@Q@F)=5LnB#h zv%%;dN&^D(^5y(yzMd2?)LwT~#!4<5W%FL)AD-jQcs&Ua_K99y z8GDw>yj?1Xm-!xk`1K@T((B10Rxmb3tYKY8z<-K6Q;jPHXxgZCT5ZFkPSQO1u(7O; zZoZUOJjj(CMu! ze?`maJXklCT4b#)rHxb3)0u>qVPH+nSe|)2#a+W7V@wL&Xokz4#D%IWWaNrTAr4uc{#zCgCK#TzYdyd;Xjs%!T%#2_%E3({D;k$(@myR zI{72BPICq%n{0^PPN(y6pkV;xn?==KvWx+dRc(RnK+(U=#NvH!D~T5%{|;=85B zPgR`V`w$YydS8Ljop)1&e<>kqooWhS5I;&yNZ`z`p~0tW?nljVYxj^DGg30}rt~e) zCz7}`VOV5d#>mC2@{RGkf;ULxMW*}m)tJgs!s{5@jz)A*o&B^%xt$TX+zK= zqj&P-_R;%_pkeSw?K?jvU{-Z8`s@`H9E(Bf#P**ntrLH_nYzmZ%Hoio0d|h15KotE z=y-}SP@axi^F}KANjO8uiSZSLe`RMv?`I{hdF}eTm7k4aK%k6r1zt>)7f`eisV}-| zGrJp}acUm1Pc4Rnoyg}v@G(q3ac3;k(Oqt%ylCb;$)*?hy<;PjG=(bU=~PX0c&t}7 z?JLjPiLJ-%v5dzFJ!-IL+rB z`YwAtN+Lxk^#YGQrajvz`%erW!9{CTKjx{n*WN5+#=jUL_;GclRmlrjoE51cN1Q)| z(mSJ97y~7v>;;FZOj;0}v+;erFm47mqH~W(lZyDs!Lu{k)6{RFqiNFdFo1%~xQgPF z<%^ld5`s$T(9N)$l~#Q~*Ydy8syeeQ8J~S5v+;4Fs?FdQrui$!W>H6O?oz;c5&o@ zb<;8JuC7hHWqXa@r9eW6aD0Bg;? zXec$FFf&x_j~Dt*sqq8i03Nzra+Fb3Ljz1$DBm}Xi=+J>nif=sqPgaYR}iW;UO%Wt z&JlUA{X+*nmJjt*mHPf-JVOrCbmq?q343z|S>puQEp0>Z*X#lWShe*}SU|}ZreExAWaKLr!@5$~M{}F-^sO@f z#u!QqeThA)Zu$+E$}ZGfNs3p{PP-)#r=Qq&%K?CL@`Gj}w~xTMK?gXv=2;++Y`zUeMqzj8#wT0N+^;~t-mUT-9PlEM~ z*8nXz6;3L=ST&H(Njq&{(FL=i@lDLoHcetDh`6AzG{(d=TY(L?xlUS8 zbsA=h6|^AocL^W}!lYsBCUe9`npU1OLqn_)|HnRaDSeHn#R&b8pz$Jq(2guMenB7< z?@urXsX)LE%ipKoa4CIt=f$Yq*O)<*Y&jOJts^!L=8$>v&pVF$tmO^Nt^Sdx*$9?_F z=Owd$8F)atGMXx6R*UFfZ+b5KKIy&&whh_)l-i6-$iqsm<-naZu9r}T@|#r7B4LZ|RHXC>T_rJDP+vGXnJ!JdONY`~k2C13SaVMoLT=1O<$%^F z)B0P$6lk#X0d3Lmqz+4k3XKR>Nn<|OB7`;}|Ms6sp39#k?=@3c>5$;NpoW?n6N$0Xwjrk>u^FhHjk<-8cwG#)T?H9s7d= zEhbUh&C?=k+X))$_@nj*YYC|O#H+4}ZT~|^5gWUm>ctetr8e*F7ID7se@WWzxCmME zxlF>D%!WUDj}U0}j;i|6&)CVIwASbm|EXi0X}4^AV!PWr)<)3yl0T|rpAm@Pv^5XO z)4pf2UjNnJ|6imIW$*uGLKesI0zuQ>zbd~9L0e2e3L2!z&$vbH%y~6HXKaWS6lX*^e?k^1!C#=Gc8DmMOX4B|lE;`wK!M#yKyoFVCrbrSQ2}0TRBP@ux|ppAuyyiHzyglCi0}|pRzK?P z4A|(H)1R;TrWPd4ZU8?nvIP!@`U&CF77B}RwC-e_Do#hrd09dQ4q$YHellyFws1RN zw!e?81}hPu6$0GjRKizf+BA=y(+&At-;7b~nFVFm{yEE6g*W zy|T?`HxEyJv|PX)&1S#H6wxx&jEAHewco2y)p+kwR$4NoE)+_f5->{IGmSGd-JEC= zwY&b6=n{6b)C)D#3!B9(qfIYyM8?n!z0C;zQ1CfHN^uj%*hEP6<`LkPDngy#i7E5~ zAR~_6^Hv>U3l9tu%qc_jUAjxo>>SDZLAn6~^OqB6{&G}RYTSw5Uto=c)0p7z_yT0X z0fQoJo$>e%I;_cRv5=6!%?{n<1D*C-fciiHCFX4kMa|P`jFA!wM5R`VccgP-%m#{Q zf=L2K|Mra6SQ(#_^aSg%N)Hor{YnqBdm^Bs4~RgeDJ9t=D0hO=&I{Mj?9gz<0;U;% z|3HX}T&*^-BPDvJ@hjpi%K3AGQiV|?`J}sx2#Ed~qMCaWpnWm*k$8olL%P`e&Ht;VDY`Zn{P7y$2gnl&mkImu_Y!3OQ^px`$7VT=?hDm0w^IY^lXPdO)Cr+;8h`!s`RVcfh_YYc zLr5eh9(=CY&85b2cL~_IEOx?T`rKYU!r~K=7kU#9r!~i;>iVbyUT5gI?&Y?Sp_ph(#I95)#JJrb)jQ(J#sOZeSt|8*`AJqKo4@k1!;2BOC zvL77oU+*WX6D3mWguRUpY>4#quRmhuQ^n$WE-aQpvCehUK6n8K45hPlLhR;=#`l$& z(apSbn!qdl+6>3DD6Xb zhCxYG8hx3@aNeVx>ew#Z^tlc$JU4e1tc~+Hq3L32g+a+?WpMAVs{zgt`MF=18rG2~M1 znNjlz?vz|!)|Dqc1<}0tcUeyI0P-D^rx-V=uWP~Am^;8MFSkB-TjW)FL5+!08Irf$ zmT7J`{>~1R?FN!gv7WSTrNrm%wV%|R*=9M)xLb!!B{9H!DV>j8%bz9rXH79wO$v9| z(K`y(&+Db1rDm?|O3u%KLqxVSvSsF@j{FlS@etN(=i38K%3bgwaWFqjIguaAmpxkT zuo9jwYViaf@VJJXesx{S!8uiUN8xI|M10M2W*?B!b5}PUN{RN7`T>FxHMf_+!TBLq zYMV5vl39!>@G038Mze4Md8jkl8V>bL-IZ$j;MG+|?}T_(aYSFaTIuucsyw8W`M&H@ zxj~fqDmN=dzAa`En4S#u&6UCab-fT;@eRE54y#*f}lvX!XnzS0)%p*pdZH;$+MI7MliRG6fWB8U2UsqiR% z*Uq^vN|}Ks#(97=A~DbGgSdTOv1KLAvQ@U(;KKE`+3%~y<*tu6Py1Ozcxc^0d4v#>fNMF7JkF{dd}jeNm^8;2!xSMIL6xqvJDJH6UMDtiJU0LEmK zMG-_bC6iz03D3f5!yaR>N>gmmAP{3fhcccbY6Hrbsvh32s5+{_D}T$oe(Vn8yA%v4 zmHnes^HqW3f}~?67xb+9gwR*orT7hp@vFyFkpzMm_$){01lb0KQgu$AxVw3)i(WaD z+>!;6u3%wBQ-ZwA6E2ieu(M^b@V3u{S(Il8k%%sR2k+Bn-6SrN@IAls&VjE9~~hziZX1WlnkQuYgUdZqmXH=j!C5QNIw>(z~fG2 zT`K`#Q%QGwQ|IK!YaFk~n_o2oB4%}6KsJ@5vILMxr+~T^{GJGEI{AB?n+Sy5Z%F2#N63kA zpCZEJHAh~-t#I)$xsXfWxMUw_gfLK2AVkwd(^~(ELfOO#aI&@ozE`EbrRsJH-ah`# z8}jv~JWjNT;D?uGyhJo$Pciu{xuq>NZ>d6jT!v2CV&BSNS5#})AFE+GwkvA|!5FP8 z<#To9)t1s|G!St5!6&@l^nB&tc>(f75XWiycYc(&{MGN8zL{UAlqA_3YIsNUP^6G@z)cl%-)417JrR`|EmK$#`HrF$Ya2dwk=z44_fJL7y+L`C-h_!7 znRosDG|v`f=l%*jdE7(&A;6{^d2 z0dg5I3oRH2LG!P)`4;SDaxGOz&g7khOOzL=T^JNVJg7|D$&^xmXYoD{kbB6juwjxK zljkLO;*9ME^BB36wZeWP-gAaoFwcH+2((!XsMfGlD(a|IeQR8}c+h^gW&20R8!}gt z6}^;3ZB)xuFx*#7^JJ$sdY3~O-85dX3-l~%2&Z-KkhZY?=O3vUE!GFLfJhGo@GLr5 z!E@OQ2fHo_-)7f0c=slo7#VLE#wpHe+QRP(fASDLl+PqqUuesp#Hnpy7gO`L{9{@m zkB@{MgUue@a_$#<40a`ZrRslEOY&Q4nt;OY^FDS`28U=SW(GqNxlKk1z(?dbNTy(+ zL|9Ys1vQA)^s)xgnqR!B0aF6jq}sbX8DI|$nITAa&@r_9+ zIeZ0TyS3W1q{6{z!?>BUz^-rfz8`rIaO-=_^L$w;egn->&{YK)eESv?!0KIw6r#Y8 z0^rWxO}u1U&^@6^y@TKNr5{9xf({mvBvKqKd z+Aq{e77k*C5N4RMGJ*JCmYY1aAe%GHS}P>QT2l}S&YD;{IW_@fTyju5t037|&W*w! z$~uX0mM0uxlfLW6(9T?P&f@TPeV`DPyOP$TZ!b5M>MtCrDlyC;tl2uAfhW??c|gO$7P=$d zDjTcFFjjRXZ4Kh^&<| z0JeF?JK~}Q`w5F=(0oA{`4`21f~Qpc`jr+WduLF~B;`>j(|!a{$qZ0KhSa#Mxul!j z&VfSoZ>k6>7j^i%)=@$M0kd}BZ-jdkxxPh^+S$^RH00v2 zlqS+zfr_OaARv){3J`-j0FfgoCVN>Sf?Cx87z+zi^gV1>J;vLRYLEr@chW1HuSboE z7>-k@6f8Vxsw+rKto-5j{DM9yT5tzh&=eXVq|MS$3!7Dx8r#_dLjm8F(aPP+oJf~8 z^2}7k72Z?cD`R$ED=v?SB3k}g{e-qq5YLR(7Rr*WpJWKqucJU#3%Y5g_C{^tX8HmM z)6q<8)QWcN`|SI)XY0iaYgrU$+sin_FH{A?rW>ljaZs{1kRq)}`zwsR9gnvX3bmKb& zMTxVUQDN3rRZDKfH&y8jwlRZ9rHv+5xa=@+0)*Lrj`R>rU_%&r7lsPL?uP4FcorZN z&JN?+da}_X{bB_`KZ~5=m>>_mw0&TkyYdq*Ldy9&?pCDJy^QzXLMVuOoOG*+Fu7eE zqut8cYWo_=@M#NOusC`HegiS{BP@hlJ~2Y+jcCDrvMb0{XHbE62pc}CnjCpkqFH>< zsitYAsZ9!}9yAG1*z_I;Jks@JB)E-IE8#6hjY)Phdv4Ygb4lRuQjedpRx-q?ye{@s z^J-Fg{-S%#U%WVKwUazk=|(AyoaApy@m|Y%TZ(mR%9Nz?XIu|f6cHDa-(;f*SfeD1 z4>X~Al4l-YHzNtOZ+N5_Tabsf6<*H|Au5mQ4K!$sQ=g#mw9_XUY*{g?-%`#T%2><7 zL>YJVC7$ttPSwf#NZwTk3uzKM2K z# zi(S=Q;>FCLs4li#@;L0nYl$BbP8y&aYoS|Nrb8T5y>OHEFQI605O`JqL)faDb)>`>i@7 zl$g#0J<@2d8mUIBehHOS-I2kqe!=9PDB6p7NUij$I_MEei*d-!+FsAJb!$Pn=mzgM zi+*8m$-bqgAJX37-g%mSHowVxm-YJCQ3SgFQwJ)#a&5EtM1GU+ljuy>}UTL9%sia>Zqd>{{wK9O>Y`4tfOfx zy|P|fC~YoK)+%rNT1iO+@s=z=IMpsxd7c>Z2nm;E9Wt5Yki1YSVQqO1;voetT-6m< zoxnm#fs3u}RicO~9O&OSwhJ!UzAAM^O^#uX)t)^^vvk!HPDRnHiU%p^9otuDI`Eafi5pQ zg*G9MFGv!mupnXIK(3Q!9dU++t_D5z#KjkIa}?M;3=mTqP*^T)p3583eUU`{2JaM+ zzwn27Eh84GlrV%&*bfCFMeG3qBP>UdleFMWvKX(iC_$SQoc=&KQYr;yn*vNMaI%sL zArMlt@}G#K$nP{B7@)Qx%23uUdRNM{sN>6gFozE3@r^;WILEkdHB+MN%Fie`*|+{*H>n&f2rrNTTR#RDLEa1LCijZ%s%o3QwR&nST*&>!m3|6kMJih>c~;zLU39;* z)VhLb+@CD~a)&qo=2JTD8t2k4tz0ADQhF)orNURoO0VRk(fnuXqhtcUbjB}_Bb{nD ztE|4m+aJGsXWy_ml+OPU_JzB}#awXh7CFdF0lj3>p{;s5MvyU=$i`XNl`<^k-Wd5g z_$u}c7=K4N<3{^xDJkkoj?#%n&KgDYxre`(+*p$UEMk=Px$v3gM;4+VfG3$H&(QqpKSC^GCmm@Q1V2AWT*^@j2cR`XIY@iIi zHc8(J69ZYn*aP3dA2W%aYy;GH!wYR5vdWQ>yWwCLPCQcA!L|VClMAq#LrxBxK0fCg z@gweA)DbsK8OuogoZBVNKh(vgtS$LRG|YK`=5L)-e92ICg0(13AQExCBfYSW!7(>+ zL06TxLO|>?D|}ii6m4@x<4l<}lG!J%oI8LoJC$57db_q_kJY7*+<}eu#C&shSGLLt zy|(C=RF&|({^mKoQ%M4;E4BT8g-o7@5#z0;3F|6Kxw^4gTPR2Vx?%r3|4VJ;mtqUz z4-&Bn44ULg^WJRy{_otkm&qt>;(A`dSxj;JZjNy0HDSccmI(gnXTt?uqM61lDJ3Ny zYcHV%CrQmmW#IzH9;PuAm;j!+z_WD?3eQlk;(~>KLU65WDcnOoNYDycv=v+x1O?8; zx?l}i80&;VUi2-%QSI<3Q{Nr%?k}8(0ARbFL$^k1)G<13%a3X!qtff;#8`dqZ~T3E zLZ5f=V)gm*$TMU!ej>@zxfj}#yiYJC-KtY(^v#hvq@Acb2tRBSeFs5^M0E#y!gyI4 z5J?P`Y%4ANc!U}v-w}>qMDi#=05+G7`B;Zuijl`mMJJ5A`Ur{8BT7nV3{h{!2g^;m zVbY5X*$fCt6S@&Nl{C9gW!v4XRa$T@jpGb)=(K4(gJKFhJR7MiBIr~0ci-UXNE_Y6 ziJ`s^Ga4~GROYv!j8*kpHXQ6&d#;omoV2F;2My+?kHkVAz62v{lGtKZKS_*MyDo5W zf1u&rKx-c@Fq0U)!Qb#}KX1=A)F140d~0UIvQE{A;uW&yw`hy}Od~cdXF#K@O&E7% z0n&m+l&raBx!r&E1}%6CFaERBwBYpwBG>RI{KVPt%%nifKDQw3>cgcq!7W3~b)S%( z%lj7n)@q&pRw@t#_qS5PuLu;Dao|&fLBARl7ZbOGFz^vi5^2`jCgbuKo zbiGRYC$C`#Ukt4o@iCqi;g9ubZuaFZZRf1$?*{Pd%ca!KF(< zE^b-ul6aYwsPQB&>HGr3B}?3uRzBr~U6$;lfd)s`N&U+}{b`Pw<}StnR&kdkaK;G; zs?G?Z`BIuR@^tt%N8qG`X3b8oY`stQ=oPw9nSbZFku>XP(w*!+WCVsR5sBoum==GT zwK)(=)}Hz!W2Dvg(D&!=^|ws}Gwl218N3~MYPA#W=SAOaMq{n&oZKQTB6l*akEhTB zRh53q_iRKCpcS=n<+o~!{=@)ovr@vhKh`?~h=PaEWK1A%?G@r?5Y`j-@z;H<%|89(z%py=YTJVomD-A7B zp)*_&xr+EmfmC}+BIpSgPCaQw(xH1LU^C&!T6b^_(|c&I;U7@NVv!me&}>KuMfDqS_hSB zNk@nD6QaKhKJ1Z^+Fvs`x85HE9qR23@(kXu2Dk`x0OvuH7D0VcbI@v;9f^_enUTpD zw722|Yfnpl5aH($*>Z)8oZ%-<>N~BaTpGf}`K8*RvYapm04Pws-FoJ zcyhPN&^@=>zKf?)cfnwfRy#I@5$}keJZ1)gSb@eJ@){$?$ZO+v)SAGI=M9TR>^_U*!Aj=9aB zKP`N#-S-9e5YN_Q-y`seJ!@xV2}>l6w&=!M4nmZF1htT6iH$7mz+8X(IRD^j;fZ$N zF{?1BXU{sSno!|3oX8nikjy5{J|gI=f5>EACevJx3WoKQihnN4vEX)|C-Ni0%okqM z^4lu<>#fG>%cYh;^I0pdYxMAVGdTNTe42RsA*<>W;?Cv1@Yq4g%N%r^+Uq&Vi438} zWhpEpyQ~$Y>K&pnxGrC0058>NIBHNp;oVhT;;f#Ls!tXhWNv-FTc4bUGvexTBERix zOF~;uEwyipI5oe~`z_!g)##g0tKs-cjb?-Ws62x&0o}spmz{AS?WQnr(CG1M3zapqxzR45>t)| z41a2+a8#X`wv#l_fFgm0g#VyQU!al zbU3I#nF>AnNiq%KEem>it?xE*BH}H4nqr~-)(A>}ct#)

xi8_O&2bA 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 N{PutT`Tr~e{|o8s+tUC5 literal 396800 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 ODJ1^*|6l*Z3j7Q3=OaD< diff --git a/build/yarn.lock b/build/yarn.lock index 5e28180033..ca72f0408f 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -3482,10 +3482,10 @@ terser@*: source-map "~0.6.1" source-map-support "~0.5.12" -terser@4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.1.tgz#09820bcb3398299c4b48d9a86aefc65127d0ed65" - integrity sha512-pnzH6dnFEsR2aa2SJaKb1uSCl3QmIsJ8dEkj0Fky+2AwMMcC9doMqLOQIH6wVTEKaVfKVvLSk5qxPBEZT9mywg== +terser@4.3.8: + version "4.3.8" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.8.tgz#707f05f3f4c1c70c840e626addfdb1c158a17136" + integrity sha512-otmIRlRVmLChAWsnSFNO0Bfk6YySuBp6G9qrHiJwlLDd4mxe2ta4sjI7TzIR+W1nBMjilzrMcPOz9pSusgx3hQ== dependencies: commander "^2.20.0" source-map "~0.6.1" @@ -3643,10 +3643,10 @@ typed-rest-client@^0.9.0: tunnel "0.0.4" underscore "1.8.3" -typescript@3.6.2: - version "3.6.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.2.tgz#105b0f1934119dde543ac8eb71af3a91009efe54" - integrity sha512-lmQ4L+J6mnu3xweP8+rOrUwzmN+MRAj7TgtJtDaXE5PMyX2kCrklhg3rvOsOIfNeAWMQWO2F1GPc1kMD2vLAfw== +typescript@3.7.0-dev.20191017: + version "3.7.0-dev.20191017" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.0-dev.20191017.tgz#e61440dd445edea6d7b9a699e7c5d5fbcd1906f2" + integrity sha512-Yi0lCPEN0cn9Gp8TEEkPpgKNR5SWAmx9Hmzzz+oEuivw6amURqRGynaLyFZkMA9iMsvYG5LLqhdlFO3uu5ZT/w== typescript@^3.0.1: version "3.5.3" diff --git a/cgmanifest.json b/cgmanifest.json index c16d88b599..c78562c41d 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "chromium", "repositoryUrl": "https://chromium.googlesource.com/chromium/src", - "commitHash": "c6a08e5368de4352903e702cde750b33239a50ab" + "commitHash": "91f08db83c2ce8c722ddf0911ead8f7c473bedfa" } }, "licenseDetail": [ @@ -40,7 +40,7 @@ "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ], "isOnlyProductionDependency": true, - "version": "69.0.3497.128" + "version": "76.0.3809.146" }, { "component": { @@ -48,11 +48,11 @@ "git": { "name": "nodejs", "repositoryUrl": "https://github.com/nodejs/node", - "commitHash": "8c70b2084ce5f76ea1e3b3c4ccdeee4483fe338b" + "commitHash": "64219741218aa87e259cf8257596073b8e747f0a" } }, "isOnlyProductionDependency": true, - "version": "10.11.0" + "version": "12.4.0" }, { "component": { @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "4e4c7527c63fcf27dffaeb58bde996b8d859c0ed" + "commitHash": "1e50380fab37f407c4d357e1e30ecbc3d5a703b8" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "4.2.10" + "version": "6.0.12" }, { "component": { @@ -96,25 +96,13 @@ "component": { "type": "git", "git": { - "name": "vscode-octicons-font", - "repositoryUrl": "https://github.com/Microsoft/vscode-octicons-font", - "commitHash": "4cbf2bd35cf0084eabd47d322cc58339fd7743cf" + "name": "vscode-codicons", + "repositoryUrl": "https://github.com/microsoft/vscode-codicons", + "commitHash": "7f14c092f65f658cd520df3f13765efe828d0ba4" } }, - "license": "MIT", - "version": "1.3.2" - }, - { - "component": { - "type": "git", - "git": { - "name": "octicons", - "repositoryUrl": "https://github.com/primer/octicons", - "commitHash": "d120bf97bc9a12fb415f69fedaf31fe58427ca56" - } - }, - "license": "MIT", - "version": "8.3.0" + "license": "MIT and Creative Commons Attribution 4.0", + "version": "0.0.1" }, { "component": { diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index e6658a6d42..bd10c038f8 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -37,7 +37,8 @@ "launch.json", "tasks.json", "keybindings.json", - "extensions.json" + "extensions.json", + "argv.json" ] } ], @@ -71,8 +72,8 @@ "url": "vscode://schemas/workspaceConfig" }, { - "fileMatch": "%APP_SETTINGS_HOME%/locale.json", - "url": "vscode://schemas/locale" + "fileMatch": "**/argv.json", + "url": "vscode://schemas/argv" }, { "fileMatch": "/.vscode/settings.json", @@ -105,6 +106,10 @@ { "fileMatch": "/.devcontainer.json", "url": "./schemas/devContainer.schema.json" + }, + { + "fileMatch": "%APP_SETTINGS_HOME%/globalStorage/ms-vscode-remote.remote-containers/imageConfigs/*.json", + "url": "./schemas/attachContainer.schema.json" } ] }, diff --git a/extensions/configuration-editing/schemas/attachContainer.schema.json b/extensions/configuration-editing/schemas/attachContainer.schema.json new file mode 100644 index 0000000000..5f3d28abb8 --- /dev/null +++ b/extensions/configuration-editing/schemas/attachContainer.schema.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/schema#", + "description": "Configures an attached to container", + "allowComments": true, + "type": "object", + "definitions": { + "attachContainer": { + "type": "object", + "properties": { + "workspaceFolder": { + "type": "string", + "description": "The path of the workspace folder inside the container." + }, + "forwardPorts": { + "type": "array", + "description": "Ports that are forwarded from the container to the local machine.", + "items": { + "type": "integer" + } + }, + "extensions": { + "type": "array", + "description": "An array of extensions that should be installed into the container.", + "items": { + "type": "string" + } + } + } + } + }, + "allOf": [ + { + "$ref": "#/definitions/attachContainer" + } + ] +} diff --git a/extensions/configuration-editing/src/extension.ts b/extensions/configuration-editing/src/extension.ts index 0d051856c2..f67105c60f 100644 --- a/extensions/configuration-editing/src/extension.ts +++ b/extensions/configuration-editing/src/extension.ts @@ -75,7 +75,8 @@ function registerVariableCompletions(pattern: string): vscode.Disposable { { label: 'fileDirname', detail: localize('fileDirname', "The current opened file's dirname") }, { label: 'fileExtname', detail: localize('fileExtname', "The current opened file's extension") }, { label: 'fileBasename', detail: localize('fileBasename', "The current opened file's basename") }, - { label: 'fileBasenameNoExtension', detail: localize('fileBasenameNoExtension', "The current opened file's basename with no file extension") } + { label: 'fileBasenameNoExtension', detail: localize('fileBasenameNoExtension', "The current opened file's basename with no file extension") }, + { label: 'defaultBuildTask', detail: localize('defaultBuildTask', "The name of the default build task. If there is not a single default build task then a quick pick is shown to choose the build task.") }, ].map(variable => ({ label: '${' + variable.label + '}', range: new vscode.Range(startPosition, position), diff --git a/extensions/git/package.json b/extensions/git/package.json index b64dfb4707..eafc491643 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -362,6 +362,11 @@ "title": "%command.ignore%", "category": "Git" }, + { + "command": "git.revealInExplorer", + "title": "%command.revealInExplorer%", + "category": "Git" + }, { "command": "git.stashIncludeUntracked", "title": "%command.stashIncludeUntracked%", @@ -507,6 +512,10 @@ "command": "git.restoreCommitTemplate", "when": "false" }, + { + "command": "git.revealInExplorer", + "when": "false" + }, { "command": "git.undoCommit", "when": "config.git.enabled && gitOpenRepositoryCount != 0" @@ -913,6 +922,11 @@ "when": "scmProvider == git && scmResourceGroup == merge", "group": "inline" }, + { + "command": "git.revealInExplorer", + "when": "scmProvider == git && scmResourceGroup == merge", + "group": "2_view" + }, { "command": "git.openFile2", "when": "scmProvider == git && scmResourceGroup == merge && config.git.showInlineOpenFileAction && config.git.openDiffOnClick", @@ -948,6 +962,11 @@ "when": "scmProvider == git && scmResourceGroup == index", "group": "inline" }, + { + "command": "git.revealInExplorer", + "when": "scmProvider == git && scmResourceGroup == index", + "group": "2_view" + }, { "command": "git.openFile2", "when": "scmProvider == git && scmResourceGroup == index && config.git.showInlineOpenFileAction && config.git.openDiffOnClick", @@ -1007,6 +1026,11 @@ "command": "git.ignore", "when": "scmProvider == git && scmResourceGroup == workingTree", "group": "1_modification@3" + }, + { + "command": "git.revealInExplorer", + "when": "scmProvider == git && scmResourceGroup == workingTree", + "group": "2_view" } ], "editor/title": [ diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 6587b65421..358f166058 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -53,9 +53,10 @@ "command.removeRemote": "Remove Remote", "command.sync": "Sync", "command.syncRebase": "Sync (Rebase)", - "command.publish": "Publish Branch", + "command.publish": "Publish Branch...", "command.showOutput": "Show Git Output", "command.ignore": "Add to .gitignore", + "command.revealInExplorer": "Reveal in Explorer", "command.stashIncludeUntracked": "Stash (Include Untracked)", "command.stash": "Stash", "command.stashPop": "Pop Stash...", diff --git a/extensions/git/src/askpass-main.ts b/extensions/git/src/askpass-main.ts index 556f918871..ec0b062ca5 100644 --- a/extensions/git/src/askpass-main.ts +++ b/extensions/git/src/askpass-main.ts @@ -28,8 +28,8 @@ function main(argv: string[]): void { return fatal('Missing pipe'); } - if (process.env['VSCODE_GIT_COMMAND'] === 'fetch') { - return fatal('Skip fetch commands'); + if (process.env['VSCODE_GIT_COMMAND'] === 'fetch' && !!process.env['VSCODE_GIT_FETCH_SILENT']) { + return fatal('Skip silent fetch commands'); } const output = process.env['VSCODE_GIT_ASKPASS_PIPE'] as string; diff --git a/extensions/git/src/autofetch.ts b/extensions/git/src/autofetch.ts index 0ab990e590..f33685868a 100644 --- a/extensions/git/src/autofetch.ts +++ b/extensions/git/src/autofetch.ts @@ -100,7 +100,7 @@ export class AutoFetcher { } try { - await this.repository.fetchDefault(); + await this.repository.fetchDefault({ silent: true }); } catch (err) { if (err.gitErrorCode === GitErrorCodes.AuthenticationFailed) { this.disable(); diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts old mode 100755 new mode 100644 index 16cdf95b99..0db1b8a022 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -132,6 +132,20 @@ class HEADItem implements QuickPickItem { get alwaysShow(): boolean { return true; } } +class AddRemoteItem implements QuickPickItem { + + constructor(private cc: CommandCenter) { } + + get label(): string { return localize('add remote', '$(plus) Add a new remote...'); } + get description(): string { return ''; } + + get alwaysShow(): boolean { return true; } + + async run(repository: Repository): Promise { + await this.cc.addRemote(repository); + } +} + interface CommandOptions { repository?: boolean; diff?: boolean; @@ -493,7 +507,7 @@ export class CommandCenter { const repositoryPath = await window.withProgress( opts, - (_, token) => this.git.clone(url!, parentPath, token) + (progress, token) => this.git.clone(url!, parentPath, progress, token) ); let message = localize('proposeopen', "Would you like to open the cloned repository?"); @@ -1249,11 +1263,13 @@ export class CommandCenter { promptToSaveFilesBeforeCommit = 'never'; } + const enableSmartCommit = config.get('enableSmartCommit') === true; + if (promptToSaveFilesBeforeCommit !== 'never') { let documents = workspace.textDocuments .filter(d => !d.isUntitled && d.isDirty && isDescendant(repository.root, d.uri.fsPath)); - if (promptToSaveFilesBeforeCommit === 'staged') { + if (promptToSaveFilesBeforeCommit === 'staged' || repository.indexGroup.resourceStates.length > 0) { documents = documents .filter(d => repository.indexGroup.resourceStates.some(s => s.resourceUri.path === d.uri.fsPath)); } @@ -1275,7 +1291,6 @@ export class CommandCenter { } } - const enableSmartCommit = config.get('enableSmartCommit') === true; const enableCommitSigning = config.get('enableCommitSigning') === true; const noStagedChanges = repository.indexGroup.resourceStates.length === 0; const noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0; @@ -1360,28 +1375,38 @@ export class CommandCenter { private async commitWithAnyInput(repository: Repository, opts?: CommitOptions): Promise { const message = repository.inputBox.value; const getCommitMessage = async () => { - if (message) { - return message; + let _message: string | undefined = message; + if (!_message) { + let value: string | undefined = undefined; + + if (opts && opts.amend && repository.HEAD && repository.HEAD.commit) { + value = (await repository.getCommit(repository.HEAD.commit)).message; + } + + const branchName = repository.headShortName; + let placeHolder: string; + + if (branchName) { + placeHolder = localize('commitMessageWithHeadLabel2', "Message (commit on '{0}')", branchName); + } else { + placeHolder = localize('commit message', "Commit message"); + } + + _message = await window.showInputBox({ + value, + placeHolder, + prompt: localize('provide commit message', "Please provide a commit message"), + ignoreFocusOut: true + }); } - let value: string | undefined = undefined; - - if (opts && opts.amend && repository.HEAD && repository.HEAD.commit) { - value = (await repository.getCommit(repository.HEAD.commit)).message; - } - - return await window.showInputBox({ - value, - placeHolder: localize('commit message', "Commit message"), - prompt: localize('provide commit message', "Please provide a commit message"), - ignoreFocusOut: true - }); + return _message ? repository.cleanUpCommitEditMessage(_message) : _message; }; const didCommit = await this.smartCommit(repository, getCommitMessage, opts); if (message && didCommit) { - repository.inputBox.value = await repository.getCommitTemplate(); + repository.inputBox.value = await repository.getInputTemplate(); } } @@ -1458,6 +1483,15 @@ export class CommandCenter { const commit = await repository.getCommit('HEAD'); + if (commit.parents.length > 1) { + const yes = localize('undo commit', "Undo merge commit"); + const result = await window.showWarningMessage(localize('merge commit', "The last commit was a merge commit. Are you sure you want to undo it?"), yes); + + if (result !== yes) { + return; + } + } + if (commit.parents.length > 0) { await repository.reset('HEAD~'); } else { @@ -1483,7 +1517,6 @@ export class CommandCenter { const quickpick = window.createQuickPick(); quickpick.items = picks; quickpick.placeholder = placeHolder; - quickpick.ignoreFocusOut = true; quickpick.show(); const choice = await new Promise(c => quickpick.onDidAccept(() => c(quickpick.activeItems[0]))); @@ -1722,7 +1755,7 @@ export class CommandCenter { const remoteRefs = repository.refs; const remoteRefsFiltered = remoteRefs.filter(r => (r.remote === remotePick.label)); - const branchPicks = remoteRefsFiltered.map(r => ({ label: r.name })) as { label: string; description: string }[]; + const branchPicks = remoteRefsFiltered.map(r => ({ label: r.name! })); const branchPlaceHolder = localize('pick branch pull', "Pick a branch to pull from"); const branchPick = await window.showQuickPick(branchPicks, { placeHolder: branchPlaceHolder }); @@ -1829,15 +1862,24 @@ export class CommandCenter { } } else { const branchName = repository.HEAD.name; - const picks = remotes.filter(r => r.pushUrl !== undefined).map(r => ({ label: r.name, description: r.pushUrl! })); + const addRemote = new AddRemoteItem(this); + const picks = [...remotes.filter(r => r.pushUrl !== undefined).map(r => ({ label: r.name, description: r.pushUrl })), addRemote]; const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName); - const pick = await window.showQuickPick(picks, { placeHolder }); + const choice = await window.showQuickPick(picks, { placeHolder }); - if (!pick) { + if (!choice) { return; } - await repository.pushTo(pick.label, branchName, undefined, forcePushMode); + if (choice === addRemote) { + const newRemote = await this.addRemote(repository); + + if (newRemote) { + await repository.pushTo(newRemote, branchName, undefined, forcePushMode); + } + } else { + await repository.pushTo(choice.label, branchName, undefined, forcePushMode); + } } } @@ -1872,7 +1914,7 @@ export class CommandCenter { } @command('git.addRemote', { repository: true }) - async addRemote(repository: Repository): Promise { + async addRemote(repository: Repository): Promise { const remotes = repository.remotes; const sanitize = (name: string) => { @@ -1914,6 +1956,8 @@ export class CommandCenter { } await repository.addRemote(name, url); + + return name; } @command('git.removeRemote', { repository: true }) @@ -2029,19 +2073,25 @@ export class CommandCenter { return; } + const addRemote = new AddRemoteItem(this); + const picks = [...repository.remotes.map(r => ({ label: r.name, description: r.pushUrl })), addRemote]; const branchName = repository.HEAD && repository.HEAD.name || ''; - const selectRemote = async () => { - const picks = repository.remotes.map(r => r.name); - const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName); - return await window.showQuickPick(picks, { placeHolder }); - }; - const choice = remotes.length === 1 ? remotes[0].name : await selectRemote(); + const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName); + const choice = await window.showQuickPick(picks, { placeHolder }); if (!choice) { return; } - await repository.pushTo(choice, branchName, true); + if (choice === addRemote) { + const newRemote = await this.addRemote(repository); + + if (newRemote) { + await repository.pushTo(newRemote, branchName, true); + } + } else { + await repository.pushTo(choice.label, branchName, true); + } } @command('git.ignore') @@ -2069,6 +2119,19 @@ export class CommandCenter { await this.runByRepository(resources, async (repository, resources) => repository.ignore(resources)); } + @command('git.revealInExplorer') + async revealInExplorer(resourceState: SourceControlResourceState): Promise { + if (!resourceState) { + return; + } + + if (!(resourceState.resourceUri instanceof Uri)) { + return; + } + + await commands.executeCommand('revealInExplorer', resourceState.resourceUri); + } + private async _stash(repository: Repository, includeUntracked = false): Promise { const noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0; const noStagedChanges = repository.indexGroup.resourceStates.length === 0; diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index a1509b746a..ca90587eaa 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -12,10 +12,12 @@ 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, splitInChunks, Limiter } from './util'; -import { CancellationToken } from 'vscode'; +import { CancellationToken, Progress } from 'vscode'; import { URI } from 'vscode-uri'; import { detectEncoding } from './encoding'; import { Ref, RefType, Branch, Remote, GitErrorCodes, LogOptions, Change, Status } from './api/git'; +import * as byline from 'byline'; +import { StringDecoder } from 'string_decoder'; // https://github.com/microsoft/vscode/issues/65693 const MAX_CLI_LENGTH = 30000; @@ -163,6 +165,7 @@ export interface SpawnOptions extends cp.SpawnOptions { encoding?: string; log?: boolean; cancellationToken?: CancellationToken; + onSpawn?: (childProcess: cp.ChildProcess) => void; } async function exec(child: cp.ChildProcess, cancellationToken?: CancellationToken): Promise> { @@ -341,7 +344,7 @@ export class Git { return; } - async clone(url: string, parentPath: string, cancellationToken?: CancellationToken): Promise { + async clone(url: string, parentPath: string, progress: Progress<{ increment: number }>, cancellationToken?: CancellationToken): Promise { let baseFolderName = decodeURI(url).replace(/[\/]+$/, '').replace(/^.*[\/\\]/, '').replace(/\.git$/, '') || 'repository'; let folderName = baseFolderName; let folderPath = path.join(parentPath, folderName); @@ -354,8 +357,36 @@ export class Git { await mkdirp(parentPath); + const onSpawn = (child: cp.ChildProcess) => { + const decoder = new StringDecoder('utf8'); + const lineStream = new byline.LineStream({ encoding: 'utf8' }); + child.stderr.on('data', (buffer: Buffer) => lineStream.write(decoder.write(buffer))); + + let totalProgress = 0; + let previousProgress = 0; + + lineStream.on('data', (line: string) => { + let match: RegExpMatchArray | null = null; + + if (match = /Counting objects:\s*(\d+)%/i.exec(line)) { + totalProgress = Math.floor(parseInt(match[1]) * 0.1); + } else if (match = /Compressing objects:\s*(\d+)%/i.exec(line)) { + totalProgress = 10 + Math.floor(parseInt(match[1]) * 0.1); + } else if (match = /Receiving objects:\s*(\d+)%/i.exec(line)) { + totalProgress = 20 + Math.floor(parseInt(match[1]) * 0.4); + } else if (match = /Resolving deltas:\s*(\d+)%/i.exec(line)) { + totalProgress = 60 + Math.floor(parseInt(match[1]) * 0.4); + } + + if (totalProgress !== previousProgress) { + progress.report({ increment: totalProgress - previousProgress }); + previousProgress = totalProgress; + } + }); + }; + try { - await this.exec(parentPath, ['clone', url.includes(' ') ? encodeURI(url) : url, folderPath], { cancellationToken }); + await this.exec(parentPath, ['clone', url.includes(' ') ? encodeURI(url) : url, folderPath, '--progress'], { cancellationToken, onSpawn }); } catch (err) { if (err.stderr) { err.stderr = err.stderr.replace(/^Cloning.+$/m, '').trim(); @@ -370,7 +401,8 @@ export class Git { async getRepositoryRoot(repositoryPath: string): Promise { const result = await this.exec(repositoryPath, ['rev-parse', '--show-toplevel']); - return path.normalize(result.stdout.trim()); + // Keep trailing spaces which are part of the directory name + return path.normalize(result.stdout.trimLeft().replace(/(\r\n|\r|\n)+$/, '')); } async getRepositoryDotGit(repositoryPath: string): Promise { @@ -401,6 +433,10 @@ export class Git { private async _exec(args: string[], options: SpawnOptions = {}): Promise> { const child = this.spawn(args, options); + if (options.onSpawn) { + options.onSpawn(child); + } + if (options.input) { child.stdin.end(options.input, 'utf8'); } @@ -1366,8 +1402,9 @@ export class Repository { await this.run(args); } - async fetch(options: { remote?: string, ref?: string, all?: boolean, prune?: boolean, depth?: number } = {}): Promise { + async fetch(options: { remote?: string, ref?: string, all?: boolean, prune?: boolean, depth?: number, silent?: boolean } = {}): Promise { const args = ['fetch']; + const spawnOptions: SpawnOptions = {}; if (options.remote) { args.push(options.remote); @@ -1387,8 +1424,12 @@ export class Repository { args.push(`--depth=${options.depth}`); } + if (options.silent) { + spawnOptions.env = { 'VSCODE_GIT_FETCH_SILENT': 'true' }; + } + try { - await this.run(args); + await this.run(args, spawnOptions); } catch (err) { if (/No remote repository specified\./.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.NoRemoteRepositorySpecified; @@ -1748,6 +1789,23 @@ export class Repository { } } + cleanupCommitEditMessage(message: string): string { + //TODO: Support core.commentChar + return message.replace(/^\s*#.*$\n?/gm, '').trim(); + } + + + async getMergeMessage(): Promise { + const mergeMsgPath = path.join(this.repositoryRoot, '.git', 'MERGE_MSG'); + + try { + const raw = await readfile(mergeMsgPath, 'utf8'); + return raw.trim(); + } catch { + return undefined; + } + } + async getCommitTemplate(): Promise { try { const result = await this.run(['config', '--get', 'commit.template']); @@ -1766,7 +1824,7 @@ export class Repository { } const raw = await readfile(templatePath, 'utf8'); - return raw.replace(/^\s*#.*$\n?/gm, ''); + return raw.trim(); } catch (err) { return ''; diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index c48d8e9209..29a8876d99 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -579,6 +579,27 @@ export class Repository implements Disposable { return this._refs; } + get headShortName(): string | undefined { + if (!this.HEAD) { + return; + } + + const HEAD = this.HEAD; + + if (HEAD.name) { + return HEAD.name; + } + + const tag = this.refs.filter(iref => iref.type === RefType.Tag && iref.commit === HEAD.commit)[0]; + const tagName = tag && tag.name; + + if (tagName) { + return tagName; + } + + return (HEAD.commit || '').substr(0, 8); + } + private _remotes: Remote[] = []; get remotes(): Remote[] { return this._remotes; @@ -729,8 +750,6 @@ export class Repository implements Disposable { const onDidChangeCountBadge = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.countBadge', root)); onDidChangeCountBadge(this.setCountBadge, this, this.disposables); this.setCountBadge(); - - this.updateCommitTemplate(); } validateInput(text: string, position: number): SourceControlInputBoxValidation | undefined { @@ -806,12 +825,14 @@ export class Repository implements Disposable { return toGitUri(uri, '', { replaceFileExtension: true }); } - private async updateCommitTemplate(): Promise { - try { - this._sourceControl.commitTemplate = await this.repository.getCommitTemplate(); - } catch (e) { - // noop + async getInputTemplate(): Promise { + const mergeMessage = await this.repository.getMergeMessage(); + + if (mergeMessage) { + return mergeMessage; } + + return await this.repository.getCommitTemplate(); } getConfigs(): Promise<{ key: string; value: string; }[]> { @@ -1033,8 +1054,8 @@ export class Repository implements Disposable { } @throttle - async fetchDefault(): Promise { - await this.run(Operation.Fetch, () => this.repository.fetch()); + async fetchDefault(options: { silent?: boolean } = {}): Promise { + await this.run(Operation.Fetch, () => this.repository.fetch(options)); } @throttle @@ -1236,6 +1257,10 @@ export class Repository implements Disposable { return await this.run(Operation.GetCommitTemplate, async () => this.repository.getCommitTemplate()); } + async cleanUpCommitEditMessage(editMessage: string): Promise { + return this.repository.cleanupCommitEditMessage(editMessage); + } + async ignore(files: Uri[]): Promise { return await this.run(Operation.Ignore, async () => { const ignoreFile = `${this.repository.root}${path.sep}.gitignore`; @@ -1457,9 +1482,9 @@ export class Repository implements Disposable { const [refs, remotes, submodules, rebaseCommit] = await Promise.all([this.repository.getRefs({ sort }), this.repository.getRemotes(), this.repository.getSubmodules(), this.getRebaseCommit()]); this._HEAD = HEAD; - this._refs = refs; - this._remotes = remotes; - this._submodules = submodules; + this._refs = refs!; + this._remotes = remotes!; + this._submodules = submodules!; this.rebaseCommit = rebaseCommit; const index: Resource[] = []; @@ -1507,6 +1532,8 @@ export class Repository implements Disposable { this.setCountBadge(); this._onDidChangeStatus.fire(); + + this._sourceControl.commitTemplate = await this.getInputTemplate(); } private setCountBadge(): void { @@ -1643,15 +1670,11 @@ export class Repository implements Disposable { } private updateInputBoxPlaceholder(): void { - const HEAD = this.HEAD; - - if (HEAD) { - const tag = this.refs.filter(iref => iref.type === RefType.Tag && iref.commit === HEAD.commit)[0]; - const tagName = tag && tag.name; - const head = HEAD.name || tagName || (HEAD.commit || '').substr(0, 8); + const branchName = this.headShortName; + if (branchName) { // '{0}' will be replaced by the corresponding key-command later in the process, which is why it needs to stay. - this._sourceControl.inputBox.placeholder = localize('commitMessageWithHeadLabel', "Message ({0} to commit on '{1}')", "{0}", head); + this._sourceControl.inputBox.placeholder = localize('commitMessageWithHeadLabel', "Message ({0} to commit on '{1}')", "{0}", branchName); } else { this._sourceControl.inputBox.placeholder = localize('commitMessage', "Message ({0} to commit)"); } diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index 1fc8553767..8f9a0e0841 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -160,7 +160,7 @@ export async function mkdirp(path: string, mode?: number): Promise { if (err.code === 'EEXIST') { const stat = await nfcall(fs.stat, path); - if (stat.isDirectory) { + if (stat.isDirectory()) { return; } diff --git a/extensions/image-preview/README.md b/extensions/image-preview/README.md index e8664c77a9..ccb5ac3954 100644 --- a/extensions/image-preview/README.md +++ b/extensions/image-preview/README.md @@ -1,3 +1,17 @@ # Image Preview **Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled. + +## Features + +This extension provides VS Code's built-in image preview functionality. + +Supported image formats: + +- `*.jpg`, `*.jpe`, `*.jpeg` +- `*.png` +- `*.bmp` +- `*.gif` +- `*.ico` +- `*.tga` +- `*.webp` diff --git a/extensions/image-preview/media/main.css b/extensions/image-preview/media/main.css index f2818ce866..67c4a53049 100644 --- a/extensions/image-preview/media/main.css +++ b/extensions/image-preview/media/main.css @@ -4,8 +4,9 @@ *--------------------------------------------------------------------------------------------*/ html, body { + width: 100%; height: 100%; - max-height: 100%; + text-align: center; } body img { @@ -77,22 +78,38 @@ body img { margin-left: 5px; } -.loading { - position: fixed; +.container.loading, +.container.error { + display: flex; + justify-content: center; + align-items: center; +} + +.loading-indicator { width: 30px; height: 30px; - left: 50%; - top: 50%; - margin-top: -15px; - margin-left: -15px; background-image: url('./loading.svg'); background-size: cover; } -.vscode-dark .loading { +.loading-indicator, +.image-load-error-message { + display: none; +} + +.loading .loading-indicator, +.error .image-load-error-message { + display: block; +} + +.image-load-error-message { + margin: 1em; +} + +.vscode-dark .loading-indicator { background-image: url('./loading-dark.svg'); } -.vscode-high-contrast .loading { +.vscode-high-contrast .loading-indicator { background-image: url('./loading-hc.svg'); } diff --git a/extensions/image-preview/media/main.js b/extensions/image-preview/media/main.js index 0f1bd15114..3b2af6e73a 100644 --- a/extensions/image-preview/media/main.js +++ b/extensions/image-preview/media/main.js @@ -70,9 +70,10 @@ let ctrlPressed = false; let altPressed = false; let hasLoadedImage = false; + let consumeClick = false; // Elements - const container = /** @type {HTMLElement} */(document.querySelector('body')); + const container = document.body; const image = document.createElement('img'); function updateScale(newScale) { @@ -88,9 +89,6 @@ image.style.width = 'auto'; vscode.setState(undefined); } else { - const oldWidth = image.width; - const oldHeight = image.height; - scale = clamp(newScale, MIN_SCALE, MAX_SCALE); if (scale >= PIXELATION_THRESHOLD) { image.classList.add('pixelated'); @@ -98,25 +96,19 @@ image.classList.remove('pixelated'); } - const { scrollTop, scrollLeft } = image.parentElement; - const dx = (scrollLeft + image.parentElement.clientWidth / 2) / image.parentElement.scrollWidth; - const dy = (scrollTop + image.parentElement.clientHeight / 2) / image.parentElement.scrollHeight; + const dx = (window.scrollX + container.clientWidth / 2) / container.scrollWidth; + const dy = (window.scrollY + container.clientHeight / 2) / container.scrollHeight; image.classList.remove('scale-to-fit'); image.style.minWidth = `${(image.naturalWidth * scale)}px`; image.style.width = `${(image.naturalWidth * scale)}px`; - const newWidth = image.width; - const scaleFactor = (newWidth - oldWidth) / oldWidth; + const newScrollX = container.scrollWidth * dx - container.clientWidth / 2; + const newScrollY = container.scrollHeight * dy - container.clientHeight / 2; - const newScrollLeft = ((oldWidth * scaleFactor * dx) + scrollLeft); - const newScrollTop = ((oldHeight * scaleFactor * dy) + scrollTop); - // scrollbar.setScrollPosition({ - // scrollLeft: newScrollLeft, - // scrollTop: newScrollTop, - // }); + window.scrollTo(newScrollX, newScrollY); - vscode.setState({ scale: scale, offsetX: newScrollLeft, offsetY: newScrollTop }); + vscode.setState({ scale: scale, offsetX: newScrollX, offsetY: newScrollY }); } vscode.postMessage({ @@ -125,6 +117,18 @@ }); } + function changeActive(value) { + if (value) { + container.classList.add('zoom-in'); + consumeClick = true; + } else { + ctrlPressed = false; + altPressed = false; + container.classList.remove('zoom-out'); + container.classList.remove('zoom-in'); + } + } + function firstZoom() { if (!image || !hasLoadedImage) { return; @@ -161,6 +165,18 @@ } }); + container.addEventListener('mousedown', (/** @type {MouseEvent} */ e) => { + if (!image || !hasLoadedImage) { + return; + } + + if (e.button !== 0) { + return; + } + + consumeClick = false; + }); + container.addEventListener('click', (/** @type {MouseEvent} */ e) => { if (!image || !hasLoadedImage) { return; @@ -170,6 +186,18 @@ return; } + ctrlPressed = e.ctrlKey; + altPressed = e.altKey; + + if (isMac ? altPressed : ctrlPressed) { + container.classList.remove('zoom-in'); + container.classList.add('zoom-out'); + } + + if (consumeClick) { + consumeClick = false; + return; + } // left click if (scale === 'fit') { firstZoom(); @@ -227,24 +255,19 @@ }); container.classList.add('image'); - container.classList.add('zoom-in'); image.classList.add('scale-to-fit'); image.addEventListener('load', () => { - document.querySelector('.loading').remove(); hasLoadedImage = true; - if (!image) { - return; - } - vscode.postMessage({ type: 'size', value: `${image.naturalWidth}x${image.naturalHeight}`, }); - container.classList.add('ready'); + document.body.classList.remove('loading'); + document.body.classList.add('ready'); document.body.append(image); updateScale(scale); @@ -254,6 +277,12 @@ } }); + image.addEventListener('error', () => { + hasLoadedImage = true; + document.body.classList.add('error'); + document.body.classList.remove('loading'); + }); + image.src = decodeURI(settings.src); window.addEventListener('message', e => { @@ -261,6 +290,9 @@ case 'setScale': updateScale(e.data.scale); break; + case 'setActive': + changeActive(e.data.value); + break; } }); }()); diff --git a/extensions/image-preview/package.json b/extensions/image-preview/package.json index 7db7b2fb17..f90152f7bc 100644 --- a/extensions/image-preview/package.json +++ b/extensions/image-preview/package.json @@ -24,9 +24,10 @@ { "viewType": "imagePreview.previewEditor", "displayName": "%webviewEditors.displayName%", + "priority": "builtin", "selector": [ { - "filenamePattern": "*.{jpg,jpe,jpeg,png,bmp,gif,ico,tga,tif,tiff,webp}", + "filenamePattern": "*.{jpg,jpe,jpeg,png,bmp,gif,ico,tga,webp}", "mime": "image/*" } ] diff --git a/extensions/image-preview/package.nls.json b/extensions/image-preview/package.nls.json index 78c753d1d5..44359cba8d 100644 --- a/extensions/image-preview/package.nls.json +++ b/extensions/image-preview/package.nls.json @@ -1,5 +1,5 @@ { "displayName": "Image Preview", - "description": "Previews images.", + "description": "Provides VS Code's built-in image preview", "webviewEditors.displayName": "Image Preview" } diff --git a/extensions/image-preview/src/preview.ts b/extensions/image-preview/src/preview.ts index d309d7ffcb..fd05fd794e 100644 --- a/extensions/image-preview/src/preview.ts +++ b/extensions/image-preview/src/preview.ts @@ -4,19 +4,32 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { SizeStatusBarEntry } from './sizeStatusBarEntry'; -import { ZoomStatusBarEntry } from './zoomStatusBarEntry'; +import * as nls from 'vscode-nls'; import { Disposable } from './dispose'; +import { SizeStatusBarEntry } from './sizeStatusBarEntry'; +import { Scale, ZoomStatusBarEntry } from './zoomStatusBarEntry'; + +const localize = nls.loadMessageBundle(); + +const enum PreviewState { + Disposed, + Visible, + Active, +} export class Preview extends Disposable { public static readonly viewType = 'imagePreview.previewEditor'; - private _active = true; + private readonly id: string = `${Date.now()}-${Math.random().toString()}`; + + private _previewState = PreviewState.Visible; + private _imageSize: string | undefined; + private _imageZoom: Scale | undefined; constructor( private readonly extensionRoot: vscode.Uri, - resource: vscode.Uri, + private readonly resource: vscode.Uri, private readonly webviewEditor: vscode.WebviewEditor, private readonly sizeStatusBarEntry: SizeStatusBarEntry, private readonly zoomStatusBarEntry: ZoomStatusBarEntry, @@ -34,56 +47,91 @@ export class Preview extends Disposable { ] }; - webviewEditor.webview.html = this.getWebiewContents(webviewEditor, resource); - this._register(webviewEditor.webview.onDidReceiveMessage(message => { switch (message.type) { case 'size': { - this.sizeStatusBarEntry.update(message.value); + this._imageSize = message.value; + this.update(); break; } case 'zoom': { - this.zoomStatusBarEntry.update(message.value); + this._imageZoom = message.value; + this.update(); break; } } })); this._register(zoomStatusBarEntry.onDidChangeScale(e => { - this.webviewEditor.webview.postMessage({ type: 'setScale', scale: e.scale }); + if (this._previewState === PreviewState.Active) { + this.webviewEditor.webview.postMessage({ type: 'setScale', scale: e.scale }); + } })); this._register(webviewEditor.onDidChangeViewState(() => { this.update(); })); + this._register(webviewEditor.onDidDispose(() => { - if (this._active) { - this.sizeStatusBarEntry.hide(); - this.zoomStatusBarEntry.hide(); + if (this._previewState === PreviewState.Active) { + this.sizeStatusBarEntry.hide(this.id); + this.zoomStatusBarEntry.hide(this.id); + } + this._previewState = PreviewState.Disposed; + })); + + const watcher = this._register(vscode.workspace.createFileSystemWatcher(resource.fsPath)); + this._register(watcher.onDidChange(e => { + if (e.toString() === this.resource.toString()) { + this.render(); } })); + this._register(watcher.onDidDelete(e => { + if (e.toString() === this.resource.toString()) { + this.webviewEditor.dispose(); + } + })); + + this.render(); this.update(); } - private update() { - this._active = this.webviewEditor.active; - if (this._active) { - this.sizeStatusBarEntry.show(); - this.zoomStatusBarEntry.show(); - } else { - this.sizeStatusBarEntry.hide(); - this.zoomStatusBarEntry.hide(); + private render() { + if (this._previewState !== PreviewState.Disposed) { + this.webviewEditor.webview.html = this.getWebiewContents(); } } - private getWebiewContents(webviewEditor: vscode.WebviewEditor, resource: vscode.Uri): string { + private update() { + if (this._previewState === PreviewState.Disposed) { + return; + } + + if (this.webviewEditor.active) { + this._previewState = PreviewState.Active; + this.sizeStatusBarEntry.show(this.id, this._imageSize || ''); + this.zoomStatusBarEntry.show(this.id, this._imageZoom || 'fit'); + } else { + if (this._previewState === PreviewState.Active) { + this.sizeStatusBarEntry.hide(this.id); + this.zoomStatusBarEntry.hide(this.id); + } + this._previewState = PreviewState.Visible; + } + this.webviewEditor.webview.postMessage({ type: 'setActive', value: this.webviewEditor.active }); + } + + private getWebiewContents(): string { + const version = Date.now().toString(); const settings = { isMac: process.platform === 'darwin', - src: this.getResourcePath(webviewEditor, resource) + src: this.getResourcePath(this.webviewEditor, this.resource, version), }; + const nonce = Date.now().toString(); + return /* html */` @@ -91,23 +139,33 @@ export class Preview extends Disposable { Image Preview - + + + - -

- + +
+
${localize('preview.imageLoadError', "An error occurred while loading the image")}
+ `; } - private getResourcePath(webviewEditor: vscode.WebviewEditor, resource: vscode.Uri) { - if (resource.scheme === 'data') { - return encodeURI(resource.toString(true)); - } + private getResourcePath(webviewEditor: vscode.WebviewEditor, resource: vscode.Uri, version: string) { + switch (resource.scheme) { + case 'data': + return encodeURI(resource.toString(true)); - return encodeURI(webviewEditor.webview.asWebviewUri(resource).toString(true)); + case 'git': + // Show blank image + return encodeURI('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAEElEQVR42gEFAPr/AP///wAI/AL+Sr4t6gAAAABJRU5ErkJggg=='); + + + default: + return encodeURI(webviewEditor.webview.asWebviewUri(resource).toString(true) + `?version=${version}`); + } } private extensionResource(path: string) { diff --git a/extensions/image-preview/src/sizeStatusBarEntry.ts b/extensions/image-preview/src/sizeStatusBarEntry.ts index 77a622c5ab..cc166dec6c 100644 --- a/extensions/image-preview/src/sizeStatusBarEntry.ts +++ b/extensions/image-preview/src/sizeStatusBarEntry.ts @@ -5,29 +5,35 @@ import * as vscode from 'vscode'; import { Disposable } from './dispose'; +import * as nls from 'vscode-nls'; + +const localize = nls.loadMessageBundle(); export class SizeStatusBarEntry extends Disposable { private readonly _entry: vscode.StatusBarItem; + private _showingOwner: string | undefined; + constructor() { super(); this._entry = this._register(vscode.window.createStatusBarItem({ id: 'imagePreview.size', - name: 'Image Size', + name: localize('sizeStatusBar.name', "Image Size"), alignment: vscode.StatusBarAlignment.Right, priority: 101 /* to the left of editor status (100) */, })); } - public show() { + public show(owner: string, text: string) { + this._showingOwner = owner; + this._entry.text = text; this._entry.show(); } - public hide() { - this._entry.hide(); - } - - public update(text: string) { - this._entry.text = text; + public hide(owner: string) { + if (owner === this._showingOwner) { + this._entry.hide(); + this._showingOwner = undefined; + } } } diff --git a/extensions/image-preview/src/zoomStatusBarEntry.ts b/extensions/image-preview/src/zoomStatusBarEntry.ts index 5226bad9de..42976b8dc5 100644 --- a/extensions/image-preview/src/zoomStatusBarEntry.ts +++ b/extensions/image-preview/src/zoomStatusBarEntry.ts @@ -11,7 +11,7 @@ const localize = nls.loadMessageBundle(); const selectZoomLevelCommandId = '_imagePreview.selectZoomLevel'; -type Scale = number | 'fit'; +export type Scale = number | 'fit'; export class ZoomStatusBarEntry extends Disposable { private readonly _entry: vscode.StatusBarItem; @@ -19,11 +19,13 @@ export class ZoomStatusBarEntry extends Disposable { private readonly _onDidChangeScale = this._register(new vscode.EventEmitter<{ scale: Scale }>()); public readonly onDidChangeScale = this._onDidChangeScale.event; + private _showOwner: string | undefined; + constructor() { super(); this._entry = this._register(vscode.window.createStatusBarItem({ id: 'imagePreview.zoom', - name: 'Image Zoom', + name: localize('zoomStatusBar.name', "Image Zoom"), alignment: vscode.StatusBarAlignment.Right, priority: 102 /* to the left of editor size entry (101) */, })); @@ -48,16 +50,17 @@ export class ZoomStatusBarEntry extends Disposable { this._entry.command = selectZoomLevelCommandId; } - public show() { + public show(owner: string, scale: Scale) { + this._showOwner = owner; + this._entry.text = this.zoomLabel(scale); this._entry.show(); } - public hide() { - this._entry.hide(); - } - - public update(scale: Scale) { - this._entry.text = this.zoomLabel(scale); + public hide(owner: string) { + if (owner === this._showOwner) { + this._entry.hide(); + this._showOwner = undefined; + } } private zoomLabel(scale: Scale): string { diff --git a/extensions/json-language-features/server/README.md b/extensions/json-language-features/server/README.md index dbc72d1460..71356e3c44 100644 --- a/extensions/json-language-features/server/README.md +++ b/extensions/json-language-features/server/README.md @@ -44,7 +44,7 @@ The JSON language server has the following dependencies on the client's capabili The client can send the following initialization options to the server: -- `provideFormatter: boolean | undefined`. If defined, the value defines wheter the server provides the `documentRangeFormattingProvider` capability on initialization. If undefined, the setting `json.format.enable` is used to determined wheter formatting is provided. The formatter will then be registered through dynamic registration. If the client does not support dynamic registration, no formatter will be available. +- `provideFormatter: boolean | undefined`. If defined, the value defines whether the server provides the `documentRangeFormattingProvider` capability on initialization. If undefined, the setting `json.format.enable` is used to determine whether formatting is provided. The formatter will then be registered through dynamic registration. If the client does not support dynamic registration, no formatter will be available. - `handledSchemaProtocols`: The URI schemas handles by the server. See section `Schema configuration` below. ### Settings @@ -60,7 +60,7 @@ The server supports the following settings: - `format` - `enable`: Whether the server should register the formatting support. This option is only applicable if the client supports *dynamicRegistration* for *rangeFormatting* and `initializationOptions.provideFormatter` is not defined. - `schema`: Configures association of file names to schema URL or schemas and/or associations of schema URL to schema content. - - `fileMatch`: an array or file names or paths (separated by `/`). `*` can be used as a wildcard. + - `fileMatch`: an array of file names or paths (separated by `/`). `*` can be used as a wildcard. - `url`: The URL of the schema, optional when also a schema is provided. - `schema`: The schema content. @@ -99,9 +99,9 @@ To find the schema for a given JSON document, the server uses the following mech - The settings define a schema association based on the documents URL. Settings can either associate a schema URL to a file or path pattern, and they can directly provide a schema. - Additionally, schema associations can also be provided by a custom 'schemaAssociations' configuration call. -Schemas are identified by URLs. To load the content of a schema, the JSON language server either tries to load from that URI or path itself, or delegates to the client. +Schemas are identified by URLs. To load the content of a schema, the JSON language server either tries to load from that URI or path itself or delegates to the client. -The `initializationOptions.handledSchemaProtocols` initialization option defines which URLs are handled by the server. Requests for all other URIs are send to the client. +The `initializationOptions.handledSchemaProtocols` initialization option defines which URLs are handled by the server. Requests for all other URIs are sent to the client. `handledSchemaProtocols` is part of the initialization options and can't be changed while the server is running. @@ -121,7 +121,7 @@ If `handledSchemaProtocols` is not set, the JSON language server will load the f #### Schema content request -Requests for schemas with URLs not handled by the server are forwarded to the client through an LSP request. This request is a JSON language server specific, non-standardized, extension to the LSP. +Requests for schemas with URLs not handled by the server are forwarded to the client through an LSP request. This request is a JSON language server-specific, non-standardized, extension to the LSP. Request: - method: 'vscode/content' @@ -130,12 +130,12 @@ Request: #### Schema content change notification -When the client is aware that a schema content has changed, it will notify the server through a notification. This notification is a JSON language server specific, non-standardized, extension to the LSP. +When the client is aware that a schema content has changed, it will notify the server through a notification. This notification is a JSON language server-specific, non-standardized, extension to the LSP. The server will, as a response, clear the schema content from the cache and reload the schema content when required again. #### Schema associations notification -In addition to the settings, schemas associations can also be provided through a notification from the client to the server. This notification is a JSON language server specific, non-standardized, extension to the LSP. +In addition to the settings, schemas associations can also be provided through a notification from the client to the server. This notification is a JSON language server-specific, non-standardized, extension to the LSP. Notification: - method: 'json/schemaAssociations' diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index 3559b36e9a..f3ddc4293b 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -1,7 +1,7 @@ { "name": "vscode-json-languageserver", "description": "JSON language server", - "version": "1.2.1", + "version": "1.2.2", "author": "Microsoft Corporation", "license": "MIT", "engines": { diff --git a/extensions/json-language-features/server/src/jsonServerMain.ts b/extensions/json-language-features/server/src/jsonServerMain.ts index f96284b56d..52427693a4 100644 --- a/extensions/json-language-features/server/src/jsonServerMain.ts +++ b/extensions/json-language-features/server/src/jsonServerMain.ts @@ -144,7 +144,7 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { } clientSnippetSupport = getClientCapability('textDocument.completion.completionItem.snippetSupport', false); - dynamicFormatterRegistration = getClientCapability('textDocument.rangeFormatting.dynamicRegistration', false) && (params.initializationOptions.provideFormatter === undefined); + dynamicFormatterRegistration = getClientCapability('textDocument.rangeFormatting.dynamicRegistration', false) && (typeof params.initializationOptions.provideFormatter !== 'boolean'); foldingRangeLimit = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE); hierarchicalDocumentSymbolSupport = getClientCapability('textDocument.documentSymbol.hierarchicalDocumentSymbolSupport', false); const capabilities: ServerCapabilities = { @@ -153,11 +153,10 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { completionProvider: clientSnippetSupport ? { resolveProvider: true, triggerCharacters: ['"', ':'] } : undefined, hoverProvider: true, documentSymbolProvider: true, - documentRangeFormattingProvider: false, + documentRangeFormattingProvider: params.initializationOptions.provideFormatter === true, colorProvider: {}, foldingRangeProvider: true, - selectionRangeProvider: true, - documentFormattingProvider: params.initializationOptions.provideFormatter === true + selectionRangeProvider: true }; return { capabilities }; diff --git a/extensions/json/language-configuration.json b/extensions/json/language-configuration.json index 9a73ac64aa..7faa70cef7 100644 --- a/extensions/json/language-configuration.json +++ b/extensions/json/language-configuration.json @@ -12,8 +12,7 @@ { "open": "[", "close": "]", "notIn": ["string"] }, { "open": "(", "close": ")", "notIn": ["string"] }, { "open": "'", "close": "'", "notIn": ["string"] }, - { "open": "/*", "close": "*/", "notIn": ["string"] }, { "open": "\"", "close": "\"", "notIn": ["string", "comment"] }, { "open": "`", "close": "`", "notIn": ["string", "comment"] } ] -} \ No newline at end of file +} diff --git a/extensions/json/package.json b/extensions/json/package.json index 6ef4b97169..e66955770b 100644 --- a/extensions/json/package.json +++ b/extensions/json/package.json @@ -27,7 +27,8 @@ ".swcrc", ".webmanifest", ".js.map", - ".css.map" + ".css.map", + ".har" ], "filenames": [ "composer.lock", diff --git a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json index 61b975b701..269140f643 100644 --- a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json +++ b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/00b05ebe6850083664d92d0eba6e5ee8f153baa6", + "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/46724e2885f9557400ed91727d75c3574ceded3a", "name": "Markdown", "scopeName": "text.html.markdown", "patterns": [ @@ -1682,6 +1682,39 @@ } ] }, + "fenced_code_block_log": { + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(log)(\\s+[^`~]*)?$)", + "name": "markup.fenced_code.block.markdown", + "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", + "beginCaptures": { + "3": { + "name": "punctuation.definition.markdown" + }, + "4": { + "name": "fenced_code.block.language.markdown" + }, + "5": { + "name": "fenced_code.block.language.attributes.markdown" + } + }, + "endCaptures": { + "3": { + "name": "punctuation.definition.markdown" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)", + "contentName": "meta.embedded.block.log", + "patterns": [ + { + "include": "text.log" + } + ] + } + ] + }, "fenced_code_block": { "patterns": [ { @@ -1831,6 +1864,9 @@ { "include": "#fenced_code_block_markdown" }, + { + "include": "#fenced_code_block_log" + }, { "include": "#fenced_code_block_unknown" } diff --git a/extensions/markdown-language-features/media/highlight.css b/extensions/markdown-language-features/media/highlight.css index 112799d466..47444a1034 100644 --- a/extensions/markdown-language-features/media/highlight.css +++ b/extensions/markdown-language-features/media/highlight.css @@ -123,26 +123,33 @@ Visual Studio-like style based on original C# coloring by Jason Diamond - * Build: `lodash modularize exports="npm" -o ./` - * Copyright jQuery Foundation and other contributors - * Released under Source EULA - * Based on Underscore.js 1.8.3 - * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - */ - -/** Used as the `TypeError` message for "Functions" methods. */ -var FUNC_ERROR_TEXT = 'Expected a function'; - -/** Used as references for various `Number` constants. */ -var NAN = 0 / 0; - -/** `Object#toString` result references. */ -var symbolTag = '[object Symbol]'; - -/** Used to match leading and trailing whitespace. */ -var reTrim = /^\s+|\s+$/g; - -/** Used to detect bad signed hexadecimal string values. */ -var reIsBadHex = /^[-+]0x[0-9a-f]+$/i; - -/** Used to detect binary string values. */ -var reIsBinary = /^0b[01]+$/i; - -/** Used to detect octal string values. */ -var reIsOctal = /^0o[0-7]+$/i; - -/** Built-in method references without a dependency on `root`. */ -var freeParseInt = parseInt; - -/** Detect free variable `global` from Node.js. */ -var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; - -/** Detect free variable `self`. */ -var freeSelf = typeof self == 'object' && self && self.Object === Object && self; - -/** Used as a reference to the global object. */ -var root = freeGlobal || freeSelf || Function('return this')(); - -/** Used for built-in method references. */ -var objectProto = Object.prototype; - -/** - * Used to resolve the - * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) - * of values. - */ -var objectToString = objectProto.toString; - -/* Built-in method references for those with the same name as other `lodash` methods. */ -var nativeMax = Math.max, - nativeMin = Math.min; - -/** - * Gets the timestamp of the number of milliseconds that have elapsed since - * the Unix epoch (1 January 1970 00:00:00 UTC). - * - * @static - * @memberOf _ - * @since 2.4.0 - * @category Date - * @returns {number} Returns the timestamp. - * @example - * - * _.defer(function(stamp) { - * console.log(_.now() - stamp); - * }, _.now()); - * // => Logs the number of milliseconds it took for the deferred invocation. - */ -var now = function() { - return root.Date.now(); -}; - -/** - * Creates a debounced function that delays invoking `func` until after `wait` - * milliseconds have elapsed since the last time the debounced function was - * invoked. The debounced function comes with a `cancel` method to cancel - * delayed `func` invocations and a `flush` method to immediately invoke them. - * Provide `options` to indicate whether `func` should be invoked on the - * leading and/or trailing edge of the `wait` timeout. The `func` is invoked - * with the last arguments provided to the debounced function. Subsequent - * calls to the debounced function return the result of the last `func` - * invocation. - * - * **Note:** If `leading` and `trailing` options are `true`, `func` is - * invoked on the trailing edge of the timeout only if the debounced function - * is invoked more than once during the `wait` timeout. - * - * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred - * until to the next tick, similar to `setTimeout` with a timeout of `0`. - * - * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) - * for details over the differences between `_.debounce` and `_.throttle`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to debounce. - * @param {number} [wait=0] The number of milliseconds to delay. - * @param {Object} [options={}] The options object. - * @param {boolean} [options.leading=false] - * Specify invoking on the leading edge of the timeout. - * @param {number} [options.maxWait] - * The maximum time `func` is allowed to be delayed before it's invoked. - * @param {boolean} [options.trailing=true] - * Specify invoking on the trailing edge of the timeout. - * @returns {Function} Returns the new debounced function. - * @example - * - * // Avoid costly calculations while the window size is in flux. - * jQuery(window).on('resize', _.debounce(calculateLayout, 150)); - * - * // Invoke `sendMail` when clicked, debouncing subsequent calls. - * jQuery(element).on('click', _.debounce(sendMail, 300, { - * 'leading': true, - * 'trailing': false - * })); - * - * // Ensure `batchLog` is invoked once after 1 second of debounced calls. - * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 }); - * var source = new EventSource('/stream'); - * jQuery(source).on('message', debounced); - * - * // Cancel the trailing debounced invocation. - * jQuery(window).on('popstate', debounced.cancel); - */ -function debounce(func, wait, options) { - var lastArgs, - lastThis, - maxWait, - result, - timerId, - lastCallTime, - lastInvokeTime = 0, - leading = false, - maxing = false, - trailing = true; - - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - wait = toNumber(wait) || 0; - if (isObject(options)) { - leading = !!options.leading; - maxing = 'maxWait' in options; - maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait; - trailing = 'trailing' in options ? !!options.trailing : trailing; - } - - function invokeFunc(time) { - var args = lastArgs, - thisArg = lastThis; - - lastArgs = lastThis = undefined; - lastInvokeTime = time; - result = func.apply(thisArg, args); - return result; - } - - function leadingEdge(time) { - // Reset any `maxWait` timer. - lastInvokeTime = time; - // Start the timer for the trailing edge. - timerId = setTimeout(timerExpired, wait); - // Invoke the leading edge. - return leading ? invokeFunc(time) : result; - } - - function remainingWait(time) { - var timeSinceLastCall = time - lastCallTime, - timeSinceLastInvoke = time - lastInvokeTime, - result = wait - timeSinceLastCall; - - return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result; - } - - function shouldInvoke(time) { - var timeSinceLastCall = time - lastCallTime, - timeSinceLastInvoke = time - lastInvokeTime; - - // Either this is the first call, activity has stopped and we're at the - // trailing edge, the system time has gone backwards and we're treating - // it as the trailing edge, or we've hit the `maxWait` limit. - return (lastCallTime === undefined || (timeSinceLastCall >= wait) || - (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait)); - } - - function timerExpired() { - var time = now(); - if (shouldInvoke(time)) { - return trailingEdge(time); - } - // Restart the timer. - timerId = setTimeout(timerExpired, remainingWait(time)); - } - - function trailingEdge(time) { - timerId = undefined; - - // Only invoke if we have `lastArgs` which means `func` has been - // debounced at least once. - if (trailing && lastArgs) { - return invokeFunc(time); - } - lastArgs = lastThis = undefined; - return result; - } - - function cancel() { - if (timerId !== undefined) { - clearTimeout(timerId); - } - lastInvokeTime = 0; - lastArgs = lastCallTime = lastThis = timerId = undefined; - } - - function flush() { - return timerId === undefined ? result : trailingEdge(now()); - } - - function debounced() { - var time = now(), - isInvoking = shouldInvoke(time); - - lastArgs = arguments; - lastThis = this; - lastCallTime = time; - - if (isInvoking) { - if (timerId === undefined) { - return leadingEdge(lastCallTime); - } - if (maxing) { - // Handle invocations in a tight loop. - timerId = setTimeout(timerExpired, wait); - return invokeFunc(lastCallTime); - } - } - if (timerId === undefined) { - timerId = setTimeout(timerExpired, wait); - } - return result; - } - debounced.cancel = cancel; - debounced.flush = flush; - return debounced; -} - -/** - * Creates a throttled function that only invokes `func` at most once per - * every `wait` milliseconds. The throttled function comes with a `cancel` - * method to cancel delayed `func` invocations and a `flush` method to - * immediately invoke them. Provide `options` to indicate whether `func` - * should be invoked on the leading and/or trailing edge of the `wait` - * timeout. The `func` is invoked with the last arguments provided to the - * throttled function. Subsequent calls to the throttled function return the - * result of the last `func` invocation. - * - * **Note:** If `leading` and `trailing` options are `true`, `func` is - * invoked on the trailing edge of the timeout only if the throttled function - * is invoked more than once during the `wait` timeout. - * - * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred - * until to the next tick, similar to `setTimeout` with a timeout of `0`. - * - * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) - * for details over the differences between `_.throttle` and `_.debounce`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to throttle. - * @param {number} [wait=0] The number of milliseconds to throttle invocations to. - * @param {Object} [options={}] The options object. - * @param {boolean} [options.leading=true] - * Specify invoking on the leading edge of the timeout. - * @param {boolean} [options.trailing=true] - * Specify invoking on the trailing edge of the timeout. - * @returns {Function} Returns the new throttled function. - * @example - * - * // Avoid excessively updating the position while scrolling. - * jQuery(window).on('scroll', _.throttle(updatePosition, 100)); - * - * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes. - * var throttled = _.throttle(renewToken, 300000, { 'trailing': false }); - * jQuery(element).on('click', throttled); - * - * // Cancel the trailing throttled invocation. - * jQuery(window).on('popstate', throttled.cancel); - */ -function throttle(func, wait, options) { - var leading = true, - trailing = true; - - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - if (isObject(options)) { - leading = 'leading' in options ? !!options.leading : leading; - trailing = 'trailing' in options ? !!options.trailing : trailing; - } - return debounce(func, wait, { - 'leading': leading, - 'maxWait': wait, - 'trailing': trailing - }); -} - -/** - * Checks if `value` is the - * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) - * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an object, else `false`. - * @example - * - * _.isObject({}); - * // => true - * - * _.isObject([1, 2, 3]); - * // => true - * - * _.isObject(_.noop); - * // => true - * - * _.isObject(null); - * // => false - */ -function isObject(value) { - var type = typeof value; - return !!value && (type == 'object' || type == 'function'); -} - -/** - * Checks if `value` is object-like. A value is object-like if it's not `null` - * and has a `typeof` result of "object". - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is object-like, else `false`. - * @example - * - * _.isObjectLike({}); - * // => true - * - * _.isObjectLike([1, 2, 3]); - * // => true - * - * _.isObjectLike(_.noop); - * // => false - * - * _.isObjectLike(null); - * // => false - */ -function isObjectLike(value) { - return !!value && typeof value == 'object'; -} - -/** - * Checks if `value` is classified as a `Symbol` primitive or object. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. - * @example - * - * _.isSymbol(Symbol.iterator); - * // => true - * - * _.isSymbol('abc'); - * // => false - */ -function isSymbol(value) { - return typeof value == 'symbol' || - (isObjectLike(value) && objectToString.call(value) == symbolTag); -} - -/** - * Converts `value` to a number. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to process. - * @returns {number} Returns the number. - * @example - * - * _.toNumber(3.2); - * // => 3.2 - * - * _.toNumber(Number.MIN_VALUE); - * // => 5e-324 - * - * _.toNumber(Infinity); - * // => Infinity - * - * _.toNumber('3.2'); - * // => 3.2 - */ -function toNumber(value) { - if (typeof value == 'number') { - return value; - } - if (isSymbol(value)) { - return NAN; - } - if (isObject(value)) { - var other = typeof value.valueOf == 'function' ? value.valueOf() : value; - value = isObject(other) ? (other + '') : other; - } - if (typeof value != 'string') { - return value === 0 ? value : +value; - } - value = value.replace(reTrim, ''); - var isBinary = reIsBinary.test(value); - return (isBinary || reIsOctal.test(value)) - ? freeParseInt(value.slice(2), isBinary ? 2 : 8) - : (reIsBadHex.test(value) ? NAN : +value); -} - -module.exports = throttle; - -/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./../webpack/buildin/global.js */ "./node_modules/webpack/buildin/global.js"))) - -/***/ }), - -/***/ "./node_modules/webpack/buildin/global.js": -/*!***********************************!*\ - !*** (webpack)/buildin/global.js ***! - \***********************************/ -/*! no static exports found */ -/***/ (function(module, exports) { - -var g; - -// This works in non-strict mode -g = (function() { - return this; -})(); - -try { - // This works if eval is allowed (see CSP) - g = g || Function("return this")() || (1, eval)("this"); -} catch (e) { - // This works if the window reference is available - if (typeof window === "object") g = window; -} - -// g can still be undefined, but nothing to do about it... -// We return undefined, instead of nothing here, so it's -// easier to handle this case. if(!global) { ...} - -module.exports = g; - - -/***/ }), - -/***/ "./preview-src/activeLineMarker.ts": -/*!*****************************************!*\ - !*** ./preview-src/activeLineMarker.ts ***! - \*****************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -const scroll_sync_1 = __webpack_require__(/*! ./scroll-sync */ "./preview-src/scroll-sync.ts"); -class ActiveLineMarker { - onDidChangeTextEditorSelection(line) { - const { previous } = scroll_sync_1.getElementsForSourceLine(line); - this._update(previous && previous.element); - } - _update(before) { - this._unmarkActiveElement(this._current); - this._markActiveElement(before); - this._current = before; - } - _unmarkActiveElement(element) { - if (!element) { - return; - } - element.className = element.className.replace(/\bcode-active-line\b/g, ''); - } - _markActiveElement(element) { - if (!element) { - return; - } - element.className += ' code-active-line'; - } -} -exports.ActiveLineMarker = ActiveLineMarker; - - -/***/ }), - -/***/ "./preview-src/events.ts": -/*!*******************************!*\ - !*** ./preview-src/events.ts ***! - \*******************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -function onceDocumentLoaded(f) { - if (document.readyState === 'loading' || document.readyState === 'uninitialized') { - document.addEventListener('DOMContentLoaded', f); - } - else { - f(); - } -} -exports.onceDocumentLoaded = onceDocumentLoaded; - - -/***/ }), - -/***/ "./preview-src/index.ts": -/*!******************************!*\ - !*** ./preview-src/index.ts ***! - \******************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -const activeLineMarker_1 = __webpack_require__(/*! ./activeLineMarker */ "./preview-src/activeLineMarker.ts"); -const events_1 = __webpack_require__(/*! ./events */ "./preview-src/events.ts"); -const messaging_1 = __webpack_require__(/*! ./messaging */ "./preview-src/messaging.ts"); -const scroll_sync_1 = __webpack_require__(/*! ./scroll-sync */ "./preview-src/scroll-sync.ts"); -const settings_1 = __webpack_require__(/*! ./settings */ "./preview-src/settings.ts"); -const throttle = __webpack_require__(/*! lodash.throttle */ "./node_modules/lodash.throttle/index.js"); -let scrollDisabled = true; -const marker = new activeLineMarker_1.ActiveLineMarker(); -const settings = settings_1.getSettings(); -const vscode = acquireVsCodeApi(); -// Set VS Code state -let state = settings_1.getData('data-state'); -vscode.setState(state); -const messaging = messaging_1.createPosterForVsCode(vscode); -window.cspAlerter.setPoster(messaging); -window.styleLoadingMonitor.setPoster(messaging); -window.onload = () => { - updateImageSizes(); -}; -events_1.onceDocumentLoaded(() => { - if (settings.scrollPreviewWithEditor) { - setTimeout(() => { - // Try to scroll to fragment if available - if (state.fragment) { - const element = scroll_sync_1.getLineElementForFragment(state.fragment); - if (element) { - scrollDisabled = true; - scroll_sync_1.scrollToRevealSourceLine(element.line); - } - } - else { - const initialLine = +settings.line; - if (!isNaN(initialLine)) { - scrollDisabled = true; - scroll_sync_1.scrollToRevealSourceLine(initialLine); - } - } - }, 0); - } -}); -const onUpdateView = (() => { - const doScroll = throttle((line) => { - scrollDisabled = true; - scroll_sync_1.scrollToRevealSourceLine(line); - }, 50); - return (line, settings) => { - if (!isNaN(line)) { - settings.line = line; - doScroll(line); - } - }; -})(); -let updateImageSizes = throttle(() => { - const imageInfo = []; - let images = document.getElementsByTagName('img'); - if (images) { - let i; - for (i = 0; i < images.length; i++) { - const img = images[i]; - if (img.classList.contains('loading')) { - img.classList.remove('loading'); - } - imageInfo.push({ - id: img.id, - height: img.height, - width: img.width - }); - } - messaging.postMessage('cacheImageSizes', imageInfo); - } -}, 50); -window.addEventListener('resize', () => { - scrollDisabled = true; - updateImageSizes(); -}, true); -window.addEventListener('message', event => { - if (event.data.source !== settings.source) { - return; - } - switch (event.data.type) { - case 'onDidChangeTextEditorSelection': - marker.onDidChangeTextEditorSelection(event.data.line); - break; - case 'updateView': - onUpdateView(event.data.line, settings); - break; - } -}, false); -document.addEventListener('dblclick', event => { - if (!settings.doubleClickToSwitchToEditor) { - return; - } - // Ignore clicks on links - for (let node = event.target; node; node = node.parentNode) { - if (node.tagName === 'A') { - return; - } - } - const offset = event.pageY; - const line = scroll_sync_1.getEditorLineNumberForPageOffset(offset); - if (typeof line === 'number' && !isNaN(line)) { - messaging.postMessage('didClick', { line: Math.floor(line) }); - } -}); -document.addEventListener('click', event => { - if (!event) { - return; - } - let node = event.target; - while (node) { - if (node.tagName && node.tagName === 'A' && node.href) { - if (node.getAttribute('href').startsWith('#')) { - break; - } - if (node.href.startsWith('file://') || node.href.startsWith('vscode-resource:') || node.href.startsWith(settings.webviewResourceRoot)) { - const [path, fragment] = node.href.replace(/^(file:\/\/|vscode-resource:)/i, '').replace(new RegExp(`^${escapeRegExp(settings.webviewResourceRoot)}`)).split('#'); - messaging.postMessage('clickLink', { path, fragment }); - event.preventDefault(); - event.stopPropagation(); - break; - } - break; - } - node = node.parentNode; - } -}, true); -if (settings.scrollEditorWithPreview) { - window.addEventListener('scroll', throttle(() => { - if (scrollDisabled) { - scrollDisabled = false; - } - else { - const line = scroll_sync_1.getEditorLineNumberForPageOffset(window.scrollY); - if (typeof line === 'number' && !isNaN(line)) { - messaging.postMessage('revealLine', { line }); - state.line = line; - vscode.setState(state); - } - } - }, 50)); -} -function escapeRegExp(text) { - return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); -} - - -/***/ }), - -/***/ "./preview-src/messaging.ts": -/*!**********************************!*\ - !*** ./preview-src/messaging.ts ***! - \**********************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -const settings_1 = __webpack_require__(/*! ./settings */ "./preview-src/settings.ts"); -exports.createPosterForVsCode = (vscode) => { - return new class { - postMessage(type, body) { - vscode.postMessage({ - type, - source: settings_1.getSettings().source, - body - }); - } - }; -}; - - -/***/ }), - -/***/ "./preview-src/scroll-sync.ts": -/*!************************************!*\ - !*** ./preview-src/scroll-sync.ts ***! - \************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -const settings_1 = __webpack_require__(/*! ./settings */ "./preview-src/settings.ts"); -function clamp(min, max, value) { - return Math.min(max, Math.max(min, value)); -} -function clampLine(line) { - return clamp(0, settings_1.getSettings().lineCount - 1, line); -} -const getCodeLineElements = (() => { - let elements; - return () => { - if (!elements) { - elements = [{ element: document.body, line: 0 }]; - for (const element of document.getElementsByClassName('code-line')) { - const line = +element.getAttribute('data-line'); - if (!isNaN(line)) { - elements.push({ element: element, line }); - } - } - } - return elements; - }; -})(); -/** - * Find the html elements that map to a specific target line in the editor. - * - * If an exact match, returns a single element. If the line is between elements, - * returns the element prior to and the element after the given line. - */ -function getElementsForSourceLine(targetLine) { - const lineNumber = Math.floor(targetLine); - const lines = getCodeLineElements(); - let previous = lines[0] || null; - for (const entry of lines) { - if (entry.line === lineNumber) { - return { previous: entry, next: undefined }; - } - else if (entry.line > lineNumber) { - return { previous, next: entry }; - } - previous = entry; - } - return { previous }; -} -exports.getElementsForSourceLine = getElementsForSourceLine; -/** - * Find the html elements that are at a specific pixel offset on the page. - */ -function getLineElementsAtPageOffset(offset) { - const lines = getCodeLineElements(); - const position = offset - window.scrollY; - let lo = -1; - let hi = lines.length - 1; - while (lo + 1 < hi) { - const mid = Math.floor((lo + hi) / 2); - const bounds = lines[mid].element.getBoundingClientRect(); - if (bounds.top + bounds.height >= position) { - hi = mid; - } - else { - lo = mid; - } - } - const hiElement = lines[hi]; - const hiBounds = hiElement.element.getBoundingClientRect(); - if (hi >= 1 && hiBounds.top > position) { - const loElement = lines[lo]; - return { previous: loElement, next: hiElement }; - } - return { previous: hiElement }; -} -exports.getLineElementsAtPageOffset = getLineElementsAtPageOffset; -/** - * Attempt to reveal the element for a source line in the editor. - */ -function scrollToRevealSourceLine(line) { - if (!settings_1.getSettings().scrollPreviewWithEditor) { - return; - } - if (line <= 0) { - window.scroll(window.scrollX, 0); - return; - } - const { previous, next } = getElementsForSourceLine(line); - if (!previous) { - return; - } - let scrollTo = 0; - const rect = previous.element.getBoundingClientRect(); - const previousTop = rect.top; - if (next && next.line !== previous.line) { - // Between two elements. Go to percentage offset between them. - const betweenProgress = (line - previous.line) / (next.line - previous.line); - const elementOffset = next.element.getBoundingClientRect().top - previousTop; - scrollTo = previousTop + betweenProgress * elementOffset; - } - else { - const progressInElement = line - Math.floor(line); - scrollTo = previousTop + (rect.height * progressInElement); - } - window.scroll(window.scrollX, Math.max(1, window.scrollY + scrollTo)); -} -exports.scrollToRevealSourceLine = scrollToRevealSourceLine; -function getEditorLineNumberForPageOffset(offset) { - const { previous, next } = getLineElementsAtPageOffset(offset); - if (previous) { - const previousBounds = previous.element.getBoundingClientRect(); - const offsetFromPrevious = (offset - window.scrollY - previousBounds.top); - if (next) { - const progressBetweenElements = offsetFromPrevious / (next.element.getBoundingClientRect().top - previousBounds.top); - const line = previous.line + progressBetweenElements * (next.line - previous.line); - return clampLine(line); - } - else { - const progressWithinElement = offsetFromPrevious / (previousBounds.height); - const line = previous.line + progressWithinElement; - return clampLine(line); - } - } - return null; -} -exports.getEditorLineNumberForPageOffset = getEditorLineNumberForPageOffset; -/** - * Try to find the html element by using a fragment id - */ -function getLineElementForFragment(fragment) { - return getCodeLineElements().find((element) => { - return element.element.id === fragment; - }); -} -exports.getLineElementForFragment = getLineElementForFragment; - - -/***/ }), - -/***/ "./preview-src/settings.ts": -/*!*********************************!*\ - !*** ./preview-src/settings.ts ***! - \*********************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -let cachedSettings = undefined; -function getData(key) { - const element = document.getElementById('vscode-markdown-preview-data'); - if (element) { - const data = element.getAttribute(key); - if (data) { - return JSON.parse(data); - } - } - throw new Error(`Could not load data for ${key}`); -} -exports.getData = getData; -function getSettings() { - if (cachedSettings) { - return cachedSettings; - } - cachedSettings = getData('data-settings'); - if (cachedSettings) { - return cachedSettings; - } - throw new Error('Could not load settings'); -} -exports.getSettings = getSettings; - - -/***/ }) - -/******/ }); -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vbm9kZV9tb2R1bGVzL2xvZGFzaC50aHJvdHRsZS9pbmRleC5qcyIsIndlYnBhY2s6Ly8vKHdlYnBhY2spL2J1aWxkaW4vZ2xvYmFsLmpzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2FjdGl2ZUxpbmVNYXJrZXIudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvZXZlbnRzLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2luZGV4LnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL21lc3NhZ2luZy50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9zY3JvbGwtc3luYy50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9zZXR0aW5ncy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7OztBQUdBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQUs7QUFDTDtBQUNBOztBQUVBO0FBQ0E7QUFDQSx5REFBaUQsY0FBYztBQUMvRDs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxtQ0FBMkIsMEJBQTBCLEVBQUU7QUFDdkQseUNBQWlDLGVBQWU7QUFDaEQ7QUFDQTtBQUNBOztBQUVBO0FBQ0EsOERBQXNELCtEQUErRDs7QUFFckg7QUFDQTs7O0FBR0E7QUFDQTs7Ozs7Ozs7Ozs7O0FDbkVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNB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Q0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw4Q0FBOEMsY0FBYztBQUM1RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwrQ0FBK0MsSUFBSTtBQUNuRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJpbmRleC5qcyIsInNvdXJjZXNDb250ZW50IjpbIiBcdC8vIFRoZSBtb2R1bGUgY2FjaGVcbiBcdHZhciBpbnN0YWxsZWRNb2R1bGVzID0ge307XG5cbiBcdC8vIFRoZSByZXF1aXJlIGZ1bmN0aW9uXG4gXHRmdW5jdGlvbiBfX3dlYnBhY2tfcmVxdWlyZV9fKG1vZHVsZUlkKSB7XG5cbiBcdFx0Ly8gQ2hlY2sgaWYgbW9kdWxlIGlzIGluIGNhY2hlXG4gXHRcdGlmKGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdKSB7XG4gXHRcdFx0cmV0dXJuIGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdLmV4cG9ydHM7XG4gXHRcdH1cbiBcdFx0Ly8gQ3JlYXRlIGEgbmV3IG1vZHVsZSAoYW5kIHB1dCBpdCBpbnRvIHRoZSBjYWNoZSlcbiBcdFx0dmFyIG1vZHVsZSA9IGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdID0ge1xuIFx0XHRcdGk6IG1vZHVsZUlkLFxuIFx0XHRcdGw6IGZhbHNlLFxuIFx0XHRcdGV4cG9ydHM6IHt9XG4gXHRcdH07XG5cbiBcdFx0Ly8gRXhlY3V0ZSB0aGUgbW9kdWxlIGZ1bmN0aW9uXG4gXHRcdG1vZHVsZXNbbW9kdWxlSWRdLmNhbGwobW9kdWxlLmV4cG9ydHMsIG1vZHVsZSwgbW9kdWxlLmV4cG9ydHMsIF9fd2VicGFja19yZXF1aXJlX18pO1xuXG4gXHRcdC8vIEZsYWcgdGhlIG1vZHVsZSBhcyBsb2FkZWRcbiBcdFx0bW9kdWxlLmwgPSB0cnVlO1xuXG4gXHRcdC8vIFJldHVybiB0aGUgZXhwb3J0cyBvZiB0aGUgbW9kdWxlXG4gXHRcdHJldHVybiBtb2R1bGUuZXhwb3J0cztcbiBcdH1cblxuXG4gXHQvLyBleHBvc2UgdGhlIG1vZHVsZXMgb2JqZWN0IChfX3dlYnBhY2tfbW9kdWxlc19fKVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5tID0gbW9kdWxlcztcblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGUgY2FjaGVcbiBcdF9fd2VicGFja19yZXF1aXJlX18uYyA9IGluc3RhbGxlZE1vZHVsZXM7XG5cbiBcdC8vIGRlZmluZSBnZXR0ZXIgZnVuY3Rpb24gZm9yIGhhcm1vbnkgZXhwb3J0c1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5kID0gZnVuY3Rpb24oZXhwb3J0cywgbmFtZSwgZ2V0dGVyKSB7XG4gXHRcdGlmKCFfX3dlYnBhY2tfcmVxdWlyZV9fLm8oZXhwb3J0cywgbmFtZSkpIHtcbiBcdFx0XHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgbmFtZSwge1xuIFx0XHRcdFx0Y29uZmlndXJhYmxlOiBmYWxzZSxcbiBcdFx0XHRcdGVudW1lcmFibGU6IHRydWUsXG4gXHRcdFx0XHRnZXQ6IGdldHRlclxuIFx0XHRcdH0pO1xuIFx0XHR9XG4gXHR9O1xuXG4gXHQvLyBkZWZpbmUgX19lc01vZHVsZSBvbiBleHBvcnRzXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnIgPSBmdW5jdGlvbihleHBvcnRzKSB7XG4gXHRcdE9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCAnX19lc01vZHVsZScsIHsgdmFsdWU6IHRydWUgfSk7XG4gXHR9O1xuXG4gXHQvLyBnZXREZWZhdWx0RXhwb3J0IGZ1bmN0aW9uIGZvciBjb21wYXRpYmlsaXR5IHdpdGggbm9uLWhhcm1vbnkgbW9kdWxlc1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5uID0gZnVuY3Rpb24obW9kdWxlKSB7XG4gXHRcdHZhciBnZXR0ZXIgPSBtb2R1bGUgJiYgbW9kdWxlLl9fZXNNb2R1bGUgP1xuIFx0XHRcdGZ1bmN0aW9uIGdldERlZmF1bHQoKSB7IHJldHVybiBtb2R1bGVbJ2RlZmF1bHQnXTsgfSA6XG4gXHRcdFx0ZnVuY3Rpb24gZ2V0TW9kdWxlRXhwb3J0cygpIHsgcmV0dXJuIG1vZHVsZTsgfTtcbiBcdFx0X193ZWJwYWNrX3JlcXVpcmVfXy5kKGdldHRlciwgJ2EnLCBnZXR0ZXIpO1xuIFx0XHRyZXR1cm4gZ2V0dGVyO1xuIFx0fTtcblxuIFx0Ly8gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm8gPSBmdW5jdGlvbihvYmplY3QsIHByb3BlcnR5KSB7IHJldHVybiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwob2JqZWN0LCBwcm9wZXJ0eSk7IH07XG5cbiBcdC8vIF9fd2VicGFja19wdWJsaWNfcGF0aF9fXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnAgPSBcIlwiO1xuXG5cbiBcdC8vIExvYWQgZW50cnkgbW9kdWxlIGFuZCByZXR1cm4gZXhwb3J0c1xuIFx0cmV0dXJuIF9fd2VicGFja19yZXF1aXJlX18oX193ZWJwYWNrX3JlcXVpcmVfXy5zID0gXCIuL3ByZXZpZXctc3JjL2luZGV4LnRzXCIpO1xuIiwiLyoqXG4gKiBsb2Rhc2ggKEN1c3RvbSBCdWlsZCkgPGh0dHBzOi8vbG9kYXNoLmNvbS8+XG4gKiBCdWlsZDogYGxvZGFzaCBtb2R1bGFyaXplIGV4cG9ydHM9XCJucG1cIiAtbyAuL2BcbiAqIENvcHlyaWdodCBqUXVlcnkgRm91bmRhdGlvbiBhbmQgb3RoZXIgY29udHJpYnV0b3JzIDxodHRwczovL2pxdWVyeS5vcmcvPlxuICogUmVsZWFzZWQgdW5kZXIgTUlUIGxpY2Vuc2UgPGh0dHBzOi8vbG9kYXNoLmNvbS9saWNlbnNlPlxuICogQmFzZWQgb24gVW5kZXJzY29yZS5qcyAxLjguMyA8aHR0cDovL3VuZGVyc2NvcmVqcy5vcmcvTElDRU5TRT5cbiAqIENvcHlyaWdodCBKZXJlbXkgQXNoa2VuYXMsIERvY3VtZW50Q2xvdWQgYW5kIEludmVzdGlnYXRpdmUgUmVwb3J0ZXJzICYgRWRpdG9yc1xuICovXG5cbi8qKiBVc2VkIGFzIHRoZSBgVHlwZUVycm9yYCBtZXNzYWdlIGZvciBcIkZ1bmN0aW9uc1wiIG1ldGhvZHMuICovXG52YXIgRlVOQ19FUlJPUl9URVhUID0gJ0V4cGVjdGVkIGEgZnVuY3Rpb24nO1xuXG4vKiogVXNlZCBhcyByZWZlcmVuY2VzIGZvciB2YXJpb3VzIGBOdW1iZXJgIGNvbnN0YW50cy4gKi9cbnZhciBOQU4gPSAwIC8gMDtcblxuLyoqIGBPYmplY3QjdG9TdHJpbmdgIHJlc3VsdCByZWZlcmVuY2VzLiAqL1xudmFyIHN5bWJvbFRhZyA9ICdbb2JqZWN0IFN5bWJvbF0nO1xuXG4vKiogVXNlZCB0byBtYXRjaCBsZWFkaW5nIGFuZCB0cmFpbGluZyB3aGl0ZXNwYWNlLiAqL1xudmFyIHJlVHJpbSA9IC9eXFxzK3xcXHMrJC9nO1xuXG4vKiogVXNlZCB0byBkZXRlY3QgYmFkIHNpZ25lZCBoZXhhZGVjaW1hbCBzdHJpbmcgdmFsdWVzLiAqL1xudmFyIHJlSXNCYWRIZXggPSAvXlstK10weFswLTlhLWZdKyQvaTtcblxuLyoqIFVzZWQgdG8gZGV0ZWN0IGJpbmFyeSBzdHJpbmcgdmFsdWVzLiAqL1xudmFyIHJlSXNCaW5hcnkgPSAvXjBiWzAxXSskL2k7XG5cbi8qKiBVc2VkIHRvIGRldGVjdCBvY3RhbCBzdHJpbmcgdmFsdWVzLiAqL1xudmFyIHJlSXNPY3RhbCA9IC9eMG9bMC03XSskL2k7XG5cbi8qKiBCdWlsdC1pbiBtZXRob2QgcmVmZXJlbmNlcyB3aXRob3V0IGEgZGVwZW5kZW5jeSBvbiBgcm9vdGAuICovXG52YXIgZnJlZVBhcnNlSW50ID0gcGFyc2VJbnQ7XG5cbi8qKiBEZXRlY3QgZnJlZSB2YXJpYWJsZSBgZ2xvYmFsYCBmcm9tIE5vZGUuanMuICovXG52YXIgZnJlZUdsb2JhbCA9IHR5cGVvZiBnbG9iYWwgPT0gJ29iamVjdCcgJiYgZ2xvYmFsICYmIGdsb2JhbC5PYmplY3QgPT09IE9iamVjdCAmJiBnbG9iYWw7XG5cbi8qKiBEZXRlY3QgZnJlZSB2YXJpYWJsZSBgc2VsZmAuICovXG52YXIgZnJlZVNlbGYgPSB0eXBlb2Ygc2VsZiA9PSAnb2JqZWN0JyAmJiBzZWxmICYmIHNlbGYuT2JqZWN0ID09PSBPYmplY3QgJiYgc2VsZjtcblxuLyoqIFVzZWQgYXMgYSByZWZlcmVuY2UgdG8gdGhlIGdsb2JhbCBvYmplY3QuICovXG52YXIgcm9vdCA9IGZyZWVHbG9iYWwgfHwgZnJlZVNlbGYgfHwgRnVuY3Rpb24oJ3JldHVybiB0aGlzJykoKTtcblxuLyoqIFVzZWQgZm9yIGJ1aWx0LWluIG1ldGhvZCByZWZlcmVuY2VzLiAqL1xudmFyIG9iamVjdFByb3RvID0gT2JqZWN0LnByb3RvdHlwZTtcblxuLyoqXG4gKiBVc2VkIHRvIHJlc29sdmUgdGhlXG4gKiBbYHRvU3RyaW5nVGFnYF0oaHR0cDovL2VjbWEtaW50ZXJuYXRpb25hbC5vcmcvZWNtYS0yNjIvNy4wLyNzZWMtb2JqZWN0LnByb3RvdHlwZS50b3N0cmluZylcbiAqIG9mIHZhbHVlcy5cbiAqL1xudmFyIG9iamVjdFRvU3RyaW5nID0gb2JqZWN0UHJvdG8udG9TdHJpbmc7XG5cbi8qIEJ1aWx0LWluIG1ldGhvZCByZWZlcmVuY2VzIGZvciB0aG9zZSB3aXRoIHRoZSBzYW1lIG5hbWUgYXMgb3RoZXIgYGxvZGFzaGAgbWV0aG9kcy4gKi9cbnZhciBuYXRpdmVNYXggPSBNYXRoLm1heCxcbiAgICBuYXRpdmVNaW4gPSBNYXRoLm1pbjtcblxuLyoqXG4gKiBHZXRzIHRoZSB0aW1lc3RhbXAgb2YgdGhlIG51bWJlciBvZiBtaWxsaXNlY29uZHMgdGhhdCBoYXZlIGVsYXBzZWQgc2luY2VcbiAqIHRoZSBVbml4IGVwb2NoICgxIEphbnVhcnkgMTk3MCAwMDowMDowMCBVVEMpLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgMi40LjBcbiAqIEBjYXRlZ29yeSBEYXRlXG4gKiBAcmV0dXJucyB7bnVtYmVyfSBSZXR1cm5zIHRoZSB0aW1lc3RhbXAuXG4gKiBAZXhhbXBsZVxuICpcbiAqIF8uZGVmZXIoZnVuY3Rpb24oc3RhbXApIHtcbiAqICAgY29uc29sZS5sb2coXy5ub3coKSAtIHN0YW1wKTtcbiAqIH0sIF8ubm93KCkpO1xuICogLy8gPT4gTG9ncyB0aGUgbnVtYmVyIG9mIG1pbGxpc2Vjb25kcyBpdCB0b29rIGZvciB0aGUgZGVmZXJyZWQgaW52b2NhdGlvbi5cbiAqL1xudmFyIG5vdyA9IGZ1bmN0aW9uKCkge1xuICByZXR1cm4gcm9vdC5EYXRlLm5vdygpO1xufTtcblxuLyoqXG4gKiBDcmVhdGVzIGEgZGVib3VuY2VkIGZ1bmN0aW9uIHRoYXQgZGVsYXlzIGludm9raW5nIGBmdW5jYCB1bnRpbCBhZnRlciBgd2FpdGBcbiAqIG1pbGxpc2Vjb25kcyBoYXZlIGVsYXBzZWQgc2luY2UgdGhlIGxhc3QgdGltZSB0aGUgZGVib3VuY2VkIGZ1bmN0aW9uIHdhc1xuICogaW52b2tlZC4gVGhlIGRlYm91bmNlZCBmdW5jdGlvbiBjb21lcyB3aXRoIGEgYGNhbmNlbGAgbWV0aG9kIHRvIGNhbmNlbFxuICogZGVsYXllZCBgZnVuY2AgaW52b2NhdGlvbnMgYW5kIGEgYGZsdXNoYCBtZXRob2QgdG8gaW1tZWRpYXRlbHkgaW52b2tlIHRoZW0uXG4gKiBQcm92aWRlIGBvcHRpb25zYCB0byBpbmRpY2F0ZSB3aGV0aGVyIGBmdW5jYCBzaG91bGQgYmUgaW52b2tlZCBvbiB0aGVcbiAqIGxlYWRpbmcgYW5kL29yIHRyYWlsaW5nIGVkZ2Ugb2YgdGhlIGB3YWl0YCB0aW1lb3V0LiBUaGUgYGZ1bmNgIGlzIGludm9rZWRcbiAqIHdpdGggdGhlIGxhc3QgYXJndW1lbnRzIHByb3ZpZGVkIHRvIHRoZSBkZWJvdW5jZWQgZnVuY3Rpb24uIFN1YnNlcXVlbnRcbiAqIGNhbGxzIHRvIHRoZSBkZWJvdW5jZWQgZnVuY3Rpb24gcmV0dXJuIHRoZSByZXN1bHQgb2YgdGhlIGxhc3QgYGZ1bmNgXG4gKiBpbnZvY2F0aW9uLlxuICpcbiAqICoqTm90ZToqKiBJZiBgbGVhZGluZ2AgYW5kIGB0cmFpbGluZ2Agb3B0aW9ucyBhcmUgYHRydWVgLCBgZnVuY2AgaXNcbiAqIGludm9rZWQgb24gdGhlIHRyYWlsaW5nIGVkZ2Ugb2YgdGhlIHRpbWVvdXQgb25seSBpZiB0aGUgZGVib3VuY2VkIGZ1bmN0aW9uXG4gKiBpcyBpbnZva2VkIG1vcmUgdGhhbiBvbmNlIGR1cmluZyB0aGUgYHdhaXRgIHRpbWVvdXQuXG4gKlxuICogSWYgYHdhaXRgIGlzIGAwYCBhbmQgYGxlYWRpbmdgIGlzIGBmYWxzZWAsIGBmdW5jYCBpbnZvY2F0aW9uIGlzIGRlZmVycmVkXG4gKiB1bnRpbCB0byB0aGUgbmV4dCB0aWNrLCBzaW1pbGFyIHRvIGBzZXRUaW1lb3V0YCB3aXRoIGEgdGltZW91dCBvZiBgMGAuXG4gKlxuICogU2VlIFtEYXZpZCBDb3JiYWNobydzIGFydGljbGVdKGh0dHBzOi8vY3NzLXRyaWNrcy5jb20vZGVib3VuY2luZy10aHJvdHRsaW5nLWV4cGxhaW5lZC1leGFtcGxlcy8pXG4gKiBmb3IgZGV0YWlscyBvdmVyIHRoZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGBfLmRlYm91bmNlYCBhbmQgYF8udGhyb3R0bGVgLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgMC4xLjBcbiAqIEBjYXRlZ29yeSBGdW5jdGlvblxuICogQHBhcmFtIHtGdW5jdGlvbn0gZnVuYyBUaGUgZnVuY3Rpb24gdG8gZGVib3VuY2UuXG4gKiBAcGFyYW0ge251bWJlcn0gW3dhaXQ9MF0gVGhlIG51bWJlciBvZiBtaWxsaXNlY29uZHMgdG8gZGVsYXkuXG4gKiBAcGFyYW0ge09iamVjdH0gW29wdGlvbnM9e31dIFRoZSBvcHRpb25zIG9iamVjdC5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW29wdGlvbnMubGVhZGluZz1mYWxzZV1cbiAqICBTcGVjaWZ5IGludm9raW5nIG9uIHRoZSBsZWFkaW5nIGVkZ2Ugb2YgdGhlIHRpbWVvdXQuXG4gKiBAcGFyYW0ge251bWJlcn0gW29wdGlvbnMubWF4V2FpdF1cbiAqICBUaGUgbWF4aW11bSB0aW1lIGBmdW5jYCBpcyBhbGxvd2VkIHRvIGJlIGRlbGF5ZWQgYmVmb3JlIGl0J3MgaW52b2tlZC5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW29wdGlvbnMudHJhaWxpbmc9dHJ1ZV1cbiAqICBTcGVjaWZ5IGludm9raW5nIG9uIHRoZSB0cmFpbGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0LlxuICogQHJldHVybnMge0Z1bmN0aW9ufSBSZXR1cm5zIHRoZSBuZXcgZGVib3VuY2VkIGZ1bmN0aW9uLlxuICogQGV4YW1wbGVcbiAqXG4gKiAvLyBBdm9pZCBjb3N0bHkgY2FsY3VsYXRpb25zIHdoaWxlIHRoZSB3aW5kb3cgc2l6ZSBpcyBpbiBmbHV4LlxuICogalF1ZXJ5KHdpbmRvdykub24oJ3Jlc2l6ZScsIF8uZGVib3VuY2UoY2FsY3VsYXRlTGF5b3V0LCAxNTApKTtcbiAqXG4gKiAvLyBJbnZva2UgYHNlbmRNYWlsYCB3aGVuIGNsaWNrZWQsIGRlYm91bmNpbmcgc3Vic2VxdWVudCBjYWxscy5cbiAqIGpRdWVyeShlbGVtZW50KS5vbignY2xpY2snLCBfLmRlYm91bmNlKHNlbmRNYWlsLCAzMDAsIHtcbiAqICAgJ2xlYWRpbmcnOiB0cnVlLFxuICogICAndHJhaWxpbmcnOiBmYWxzZVxuICogfSkpO1xuICpcbiAqIC8vIEVuc3VyZSBgYmF0Y2hMb2dgIGlzIGludm9rZWQgb25jZSBhZnRlciAxIHNlY29uZCBvZiBkZWJvdW5jZWQgY2FsbHMuXG4gKiB2YXIgZGVib3VuY2VkID0gXy5kZWJvdW5jZShiYXRjaExvZywgMjUwLCB7ICdtYXhXYWl0JzogMTAwMCB9KTtcbiAqIHZhciBzb3VyY2UgPSBuZXcgRXZlbnRTb3VyY2UoJy9zdHJlYW0nKTtcbiAqIGpRdWVyeShzb3VyY2UpLm9uKCdtZXNzYWdlJywgZGVib3VuY2VkKTtcbiAqXG4gKiAvLyBDYW5jZWwgdGhlIHRyYWlsaW5nIGRlYm91bmNlZCBpbnZvY2F0aW9uLlxuICogalF1ZXJ5KHdpbmRvdykub24oJ3BvcHN0YXRlJywgZGVib3VuY2VkLmNhbmNlbCk7XG4gKi9cbmZ1bmN0aW9uIGRlYm91bmNlKGZ1bmMsIHdhaXQsIG9wdGlvbnMpIHtcbiAgdmFyIGxhc3RBcmdzLFxuICAgICAgbGFzdFRoaXMsXG4gICAgICBtYXhXYWl0LFxuICAgICAgcmVzdWx0LFxuICAgICAgdGltZXJJZCxcbiAgICAgIGxhc3RDYWxsVGltZSxcbiAgICAgIGxhc3RJbnZva2VUaW1lID0gMCxcbiAgICAgIGxlYWRpbmcgPSBmYWxzZSxcbiAgICAgIG1heGluZyA9IGZhbHNlLFxuICAgICAgdHJhaWxpbmcgPSB0cnVlO1xuXG4gIGlmICh0eXBlb2YgZnVuYyAhPSAnZnVuY3Rpb24nKSB7XG4gICAgdGhyb3cgbmV3IFR5cGVFcnJvcihGVU5DX0VSUk9SX1RFWFQpO1xuICB9XG4gIHdhaXQgPSB0b051bWJlcih3YWl0KSB8fCAwO1xuICBpZiAoaXNPYmplY3Qob3B0aW9ucykpIHtcbiAgICBsZWFkaW5nID0gISFvcHRpb25zLmxlYWRpbmc7XG4gICAgbWF4aW5nID0gJ21heFdhaXQnIGluIG9wdGlvbnM7XG4gICAgbWF4V2FpdCA9IG1heGluZyA/IG5hdGl2ZU1heCh0b051bWJlcihvcHRpb25zLm1heFdhaXQpIHx8IDAsIHdhaXQpIDogbWF4V2FpdDtcbiAgICB0cmFpbGluZyA9ICd0cmFpbGluZycgaW4gb3B0aW9ucyA/ICEhb3B0aW9ucy50cmFpbGluZyA6IHRyYWlsaW5nO1xuICB9XG5cbiAgZnVuY3Rpb24gaW52b2tlRnVuYyh0aW1lKSB7XG4gICAgdmFyIGFyZ3MgPSBsYXN0QXJncyxcbiAgICAgICAgdGhpc0FyZyA9IGxhc3RUaGlzO1xuXG4gICAgbGFzdEFyZ3MgPSBsYXN0VGhpcyA9IHVuZGVmaW5lZDtcbiAgICBsYXN0SW52b2tlVGltZSA9IHRpbWU7XG4gICAgcmVzdWx0ID0gZnVuYy5hcHBseSh0aGlzQXJnLCBhcmdzKTtcbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG5cbiAgZnVuY3Rpb24gbGVhZGluZ0VkZ2UodGltZSkge1xuICAgIC8vIFJlc2V0IGFueSBgbWF4V2FpdGAgdGltZXIuXG4gICAgbGFzdEludm9rZVRpbWUgPSB0aW1lO1xuICAgIC8vIFN0YXJ0IHRoZSB0aW1lciBmb3IgdGhlIHRyYWlsaW5nIGVkZ2UuXG4gICAgdGltZXJJZCA9IHNldFRpbWVvdXQodGltZXJFeHBpcmVkLCB3YWl0KTtcbiAgICAvLyBJbnZva2UgdGhlIGxlYWRpbmcgZWRnZS5cbiAgICByZXR1cm4gbGVhZGluZyA/IGludm9rZUZ1bmModGltZSkgOiByZXN1bHQ7XG4gIH1cblxuICBmdW5jdGlvbiByZW1haW5pbmdXYWl0KHRpbWUpIHtcbiAgICB2YXIgdGltZVNpbmNlTGFzdENhbGwgPSB0aW1lIC0gbGFzdENhbGxUaW1lLFxuICAgICAgICB0aW1lU2luY2VMYXN0SW52b2tlID0gdGltZSAtIGxhc3RJbnZva2VUaW1lLFxuICAgICAgICByZXN1bHQgPSB3YWl0IC0gdGltZVNpbmNlTGFzdENhbGw7XG5cbiAgICByZXR1cm4gbWF4aW5nID8gbmF0aXZlTWluKHJlc3VsdCwgbWF4V2FpdCAtIHRpbWVTaW5jZUxhc3RJbnZva2UpIDogcmVzdWx0O1xuICB9XG5cbiAgZnVuY3Rpb24gc2hvdWxkSW52b2tlKHRpbWUpIHtcbiAgICB2YXIgdGltZVNpbmNlTGFzdENhbGwgPSB0aW1lIC0gbGFzdENhbGxUaW1lLFxuICAgICAgICB0aW1lU2luY2VMYXN0SW52b2tlID0gdGltZSAtIGxhc3RJbnZva2VUaW1lO1xuXG4gICAgLy8gRWl0aGVyIHRoaXMgaXMgdGhlIGZpcnN0IGNhbGwsIGFjdGl2aXR5IGhhcyBzdG9wcGVkIGFuZCB3ZSdyZSBhdCB0aGVcbiAgICAvLyB0cmFpbGluZyBlZGdlLCB0aGUgc3lzdGVtIHRpbWUgaGFzIGdvbmUgYmFja3dhcmRzIGFuZCB3ZSdyZSB0cmVhdGluZ1xuICAgIC8vIGl0IGFzIHRoZSB0cmFpbGluZyBlZGdlLCBvciB3ZSd2ZSBoaXQgdGhlIGBtYXhXYWl0YCBsaW1pdC5cbiAgICByZXR1cm4gKGxhc3RDYWxsVGltZSA9PT0gdW5kZWZpbmVkIHx8ICh0aW1lU2luY2VMYXN0Q2FsbCA+PSB3YWl0KSB8fFxuICAgICAgKHRpbWVTaW5jZUxhc3RDYWxsIDwgMCkgfHwgKG1heGluZyAmJiB0aW1lU2luY2VMYXN0SW52b2tlID49IG1heFdhaXQpKTtcbiAgfVxuXG4gIGZ1bmN0aW9uIHRpbWVyRXhwaXJlZCgpIHtcbiAgICB2YXIgdGltZSA9IG5vdygpO1xuICAgIGlmIChzaG91bGRJbnZva2UodGltZSkpIHtcbiAgICAgIHJldHVybiB0cmFpbGluZ0VkZ2UodGltZSk7XG4gICAgfVxuICAgIC8vIFJlc3RhcnQgdGhlIHRpbWVyLlxuICAgIHRpbWVySWQgPSBzZXRUaW1lb3V0KHRpbWVyRXhwaXJlZCwgcmVtYWluaW5nV2FpdCh0aW1lKSk7XG4gIH1cblxuICBmdW5jdGlvbiB0cmFpbGluZ0VkZ2UodGltZSkge1xuICAgIHRpbWVySWQgPSB1bmRlZmluZWQ7XG5cbiAgICAvLyBPbmx5IGludm9rZSBpZiB3ZSBoYXZlIGBsYXN0QXJnc2Agd2hpY2ggbWVhbnMgYGZ1bmNgIGhhcyBiZWVuXG4gICAgLy8gZGVib3VuY2VkIGF0IGxlYXN0IG9uY2UuXG4gICAgaWYgKHRyYWlsaW5nICYmIGxhc3RBcmdzKSB7XG4gICAgICByZXR1cm4gaW52b2tlRnVuYyh0aW1lKTtcbiAgICB9XG4gICAgbGFzdEFyZ3MgPSBsYXN0VGhpcyA9IHVuZGVmaW5lZDtcbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG5cbiAgZnVuY3Rpb24gY2FuY2VsKCkge1xuICAgIGlmICh0aW1lcklkICE9PSB1bmRlZmluZWQpIHtcbiAgICAgIGNsZWFyVGltZW91dCh0aW1lcklkKTtcbiAgICB9XG4gICAgbGFzdEludm9rZVRpbWUgPSAwO1xuICAgIGxhc3RBcmdzID0gbGFzdENhbGxUaW1lID0gbGFzdFRoaXMgPSB0aW1lcklkID0gdW5kZWZpbmVkO1xuICB9XG5cbiAgZnVuY3Rpb24gZmx1c2goKSB7XG4gICAgcmV0dXJuIHRpbWVySWQgPT09IHVuZGVmaW5lZCA/IHJlc3VsdCA6IHRyYWlsaW5nRWRnZShub3coKSk7XG4gIH1cblxuICBmdW5jdGlvbiBkZWJvdW5jZWQoKSB7XG4gICAgdmFyIHRpbWUgPSBub3coKSxcbiAgICAgICAgaXNJbnZva2luZyA9IHNob3VsZEludm9rZSh0aW1lKTtcblxuICAgIGxhc3RBcmdzID0gYXJndW1lbnRzO1xuICAgIGxhc3RUaGlzID0gdGhpcztcbiAgICBsYXN0Q2FsbFRpbWUgPSB0aW1lO1xuXG4gICAgaWYgKGlzSW52b2tpbmcpIHtcbiAgICAgIGlmICh0aW1lcklkID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgcmV0dXJuIGxlYWRpbmdFZGdlKGxhc3RDYWxsVGltZSk7XG4gICAgICB9XG4gICAgICBpZiAobWF4aW5nKSB7XG4gICAgICAgIC8vIEhhbmRsZSBpbnZvY2F0aW9ucyBpbiBhIHRpZ2h0IGxvb3AuXG4gICAgICAgIHRpbWVySWQgPSBzZXRUaW1lb3V0KHRpbWVyRXhwaXJlZCwgd2FpdCk7XG4gICAgICAgIHJldHVybiBpbnZva2VGdW5jKGxhc3RDYWxsVGltZSk7XG4gICAgICB9XG4gICAgfVxuICAgIGlmICh0aW1lcklkID09PSB1bmRlZmluZWQpIHtcbiAgICAgIHRpbWVySWQgPSBzZXRUaW1lb3V0KHRpbWVyRXhwaXJlZCwgd2FpdCk7XG4gICAgfVxuICAgIHJldHVybiByZXN1bHQ7XG4gIH1cbiAgZGVib3VuY2VkLmNhbmNlbCA9IGNhbmNlbDtcbiAgZGVib3VuY2VkLmZsdXNoID0gZmx1c2g7XG4gIHJldHVybiBkZWJvdW5jZWQ7XG59XG5cbi8qKlxuICogQ3JlYXRlcyBhIHRocm90dGxlZCBmdW5jdGlvbiB0aGF0IG9ubHkgaW52b2tlcyBgZnVuY2AgYXQgbW9zdCBvbmNlIHBlclxuICogZXZlcnkgYHdhaXRgIG1pbGxpc2Vjb25kcy4gVGhlIHRocm90dGxlZCBmdW5jdGlvbiBjb21lcyB3aXRoIGEgYGNhbmNlbGBcbiAqIG1ldGhvZCB0byBjYW5jZWwgZGVsYXllZCBgZnVuY2AgaW52b2NhdGlvbnMgYW5kIGEgYGZsdXNoYCBtZXRob2QgdG9cbiAqIGltbWVkaWF0ZWx5IGludm9rZSB0aGVtLiBQcm92aWRlIGBvcHRpb25zYCB0byBpbmRpY2F0ZSB3aGV0aGVyIGBmdW5jYFxuICogc2hvdWxkIGJlIGludm9rZWQgb24gdGhlIGxlYWRpbmcgYW5kL29yIHRyYWlsaW5nIGVkZ2Ugb2YgdGhlIGB3YWl0YFxuICogdGltZW91dC4gVGhlIGBmdW5jYCBpcyBpbnZva2VkIHdpdGggdGhlIGxhc3QgYXJndW1lbnRzIHByb3ZpZGVkIHRvIHRoZVxuICogdGhyb3R0bGVkIGZ1bmN0aW9uLiBTdWJzZXF1ZW50IGNhbGxzIHRvIHRoZSB0aHJvdHRsZWQgZnVuY3Rpb24gcmV0dXJuIHRoZVxuICogcmVzdWx0IG9mIHRoZSBsYXN0IGBmdW5jYCBpbnZvY2F0aW9uLlxuICpcbiAqICoqTm90ZToqKiBJZiBgbGVhZGluZ2AgYW5kIGB0cmFpbGluZ2Agb3B0aW9ucyBhcmUgYHRydWVgLCBgZnVuY2AgaXNcbiAqIGludm9rZWQgb24gdGhlIHRyYWlsaW5nIGVkZ2Ugb2YgdGhlIHRpbWVvdXQgb25seSBpZiB0aGUgdGhyb3R0bGVkIGZ1bmN0aW9uXG4gKiBpcyBpbnZva2VkIG1vcmUgdGhhbiBvbmNlIGR1cmluZyB0aGUgYHdhaXRgIHRpbWVvdXQuXG4gKlxuICogSWYgYHdhaXRgIGlzIGAwYCBhbmQgYGxlYWRpbmdgIGlzIGBmYWxzZWAsIGBmdW5jYCBpbnZvY2F0aW9uIGlzIGRlZmVycmVkXG4gKiB1bnRpbCB0byB0aGUgbmV4dCB0aWNrLCBzaW1pbGFyIHRvIGBzZXRUaW1lb3V0YCB3aXRoIGEgdGltZW91dCBvZiBgMGAuXG4gKlxuICogU2VlIFtEYXZpZCBDb3JiYWNobydzIGFydGljbGVdKGh0dHBzOi8vY3NzLXRyaWNrcy5jb20vZGVib3VuY2luZy10aHJvdHRsaW5nLWV4cGxhaW5lZC1leGFtcGxlcy8pXG4gKiBmb3IgZGV0YWlscyBvdmVyIHRoZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGBfLnRocm90dGxlYCBhbmQgYF8uZGVib3VuY2VgLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgMC4xLjBcbiAqIEBjYXRlZ29yeSBGdW5jdGlvblxuICogQHBhcmFtIHtGdW5jdGlvbn0gZnVuYyBUaGUgZnVuY3Rpb24gdG8gdGhyb3R0bGUuXG4gKiBAcGFyYW0ge251bWJlcn0gW3dhaXQ9MF0gVGhlIG51bWJlciBvZiBtaWxsaXNlY29uZHMgdG8gdGhyb3R0bGUgaW52b2NhdGlvbnMgdG8uXG4gKiBAcGFyYW0ge09iamVjdH0gW29wdGlvbnM9e31dIFRoZSBvcHRpb25zIG9iamVjdC5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW29wdGlvbnMubGVhZGluZz10cnVlXVxuICogIFNwZWNpZnkgaW52b2tpbmcgb24gdGhlIGxlYWRpbmcgZWRnZSBvZiB0aGUgdGltZW91dC5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW29wdGlvbnMudHJhaWxpbmc9dHJ1ZV1cbiAqICBTcGVjaWZ5IGludm9raW5nIG9uIHRoZSB0cmFpbGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0LlxuICogQHJldHVybnMge0Z1bmN0aW9ufSBSZXR1cm5zIHRoZSBuZXcgdGhyb3R0bGVkIGZ1bmN0aW9uLlxuICogQGV4YW1wbGVcbiAqXG4gKiAvLyBBdm9pZCBleGNlc3NpdmVseSB1cGRhdGluZyB0aGUgcG9zaXRpb24gd2hpbGUgc2Nyb2xsaW5nLlxuICogalF1ZXJ5KHdpbmRvdykub24oJ3Njcm9sbCcsIF8udGhyb3R0bGUodXBkYXRlUG9zaXRpb24sIDEwMCkpO1xuICpcbiAqIC8vIEludm9rZSBgcmVuZXdUb2tlbmAgd2hlbiB0aGUgY2xpY2sgZXZlbnQgaXMgZmlyZWQsIGJ1dCBub3QgbW9yZSB0aGFuIG9uY2UgZXZlcnkgNSBtaW51dGVzLlxuICogdmFyIHRocm90dGxlZCA9IF8udGhyb3R0bGUocmVuZXdUb2tlbiwgMzAwMDAwLCB7ICd0cmFpbGluZyc6IGZhbHNlIH0pO1xuICogalF1ZXJ5KGVsZW1lbnQpLm9uKCdjbGljaycsIHRocm90dGxlZCk7XG4gKlxuICogLy8gQ2FuY2VsIHRoZSB0cmFpbGluZyB0aHJvdHRsZWQgaW52b2NhdGlvbi5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdwb3BzdGF0ZScsIHRocm90dGxlZC5jYW5jZWwpO1xuICovXG5mdW5jdGlvbiB0aHJvdHRsZShmdW5jLCB3YWl0LCBvcHRpb25zKSB7XG4gIHZhciBsZWFkaW5nID0gdHJ1ZSxcbiAgICAgIHRyYWlsaW5nID0gdHJ1ZTtcblxuICBpZiAodHlwZW9mIGZ1bmMgIT0gJ2Z1bmN0aW9uJykge1xuICAgIHRocm93IG5ldyBUeXBlRXJyb3IoRlVOQ19FUlJPUl9URVhUKTtcbiAgfVxuICBpZiAoaXNPYmplY3Qob3B0aW9ucykpIHtcbiAgICBsZWFkaW5nID0gJ2xlYWRpbmcnIGluIG9wdGlvbnMgPyAhIW9wdGlvbnMubGVhZGluZyA6IGxlYWRpbmc7XG4gICAgdHJhaWxpbmcgPSAndHJhaWxpbmcnIGluIG9wdGlvbnMgPyAhIW9wdGlvbnMudHJhaWxpbmcgOiB0cmFpbGluZztcbiAgfVxuICByZXR1cm4gZGVib3VuY2UoZnVuYywgd2FpdCwge1xuICAgICdsZWFkaW5nJzogbGVhZGluZyxcbiAgICAnbWF4V2FpdCc6IHdhaXQsXG4gICAgJ3RyYWlsaW5nJzogdHJhaWxpbmdcbiAgfSk7XG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIGB2YWx1ZWAgaXMgdGhlXG4gKiBbbGFuZ3VhZ2UgdHlwZV0oaHR0cDovL3d3dy5lY21hLWludGVybmF0aW9uYWwub3JnL2VjbWEtMjYyLzcuMC8jc2VjLWVjbWFzY3JpcHQtbGFuZ3VhZ2UtdHlwZXMpXG4gKiBvZiBgT2JqZWN0YC4gKGUuZy4gYXJyYXlzLCBmdW5jdGlvbnMsIG9iamVjdHMsIHJlZ2V4ZXMsIGBuZXcgTnVtYmVyKDApYCwgYW5kIGBuZXcgU3RyaW5nKCcnKWApXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBzaW5jZSAwLjEuMFxuICogQGNhdGVnb3J5IExhbmdcbiAqIEBwYXJhbSB7Kn0gdmFsdWUgVGhlIHZhbHVlIHRvIGNoZWNrLlxuICogQHJldHVybnMge2Jvb2xlYW59IFJldHVybnMgYHRydWVgIGlmIGB2YWx1ZWAgaXMgYW4gb2JqZWN0LCBlbHNlIGBmYWxzZWAuXG4gKiBAZXhhbXBsZVxuICpcbiAqIF8uaXNPYmplY3Qoe30pO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNPYmplY3QoWzEsIDIsIDNdKTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0KF8ubm9vcCk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc09iamVjdChudWxsKTtcbiAqIC8vID0+IGZhbHNlXG4gKi9cbmZ1bmN0aW9uIGlzT2JqZWN0KHZhbHVlKSB7XG4gIHZhciB0eXBlID0gdHlwZW9mIHZhbHVlO1xuICByZXR1cm4gISF2YWx1ZSAmJiAodHlwZSA9PSAnb2JqZWN0JyB8fCB0eXBlID09ICdmdW5jdGlvbicpO1xufVxuXG4vKipcbiAqIENoZWNrcyBpZiBgdmFsdWVgIGlzIG9iamVjdC1saWtlLiBBIHZhbHVlIGlzIG9iamVjdC1saWtlIGlmIGl0J3Mgbm90IGBudWxsYFxuICogYW5kIGhhcyBhIGB0eXBlb2ZgIHJlc3VsdCBvZiBcIm9iamVjdFwiLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgNC4wLjBcbiAqIEBjYXRlZ29yeSBMYW5nXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBjaGVjay5cbiAqIEByZXR1cm5zIHtib29sZWFufSBSZXR1cm5zIGB0cnVlYCBpZiBgdmFsdWVgIGlzIG9iamVjdC1saWtlLCBlbHNlIGBmYWxzZWAuXG4gKiBAZXhhbXBsZVxuICpcbiAqIF8uaXNPYmplY3RMaWtlKHt9KTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZShbMSwgMiwgM10pO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNPYmplY3RMaWtlKF8ubm9vcCk7XG4gKiAvLyA9PiBmYWxzZVxuICpcbiAqIF8uaXNPYmplY3RMaWtlKG51bGwpO1xuICogLy8gPT4gZmFsc2VcbiAqL1xuZnVuY3Rpb24gaXNPYmplY3RMaWtlKHZhbHVlKSB7XG4gIHJldHVybiAhIXZhbHVlICYmIHR5cGVvZiB2YWx1ZSA9PSAnb2JqZWN0Jztcbn1cblxuLyoqXG4gKiBDaGVja3MgaWYgYHZhbHVlYCBpcyBjbGFzc2lmaWVkIGFzIGEgYFN5bWJvbGAgcHJpbWl0aXZlIG9yIG9iamVjdC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDQuMC4wXG4gKiBAY2F0ZWdvcnkgTGFuZ1xuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gY2hlY2suXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gUmV0dXJucyBgdHJ1ZWAgaWYgYHZhbHVlYCBpcyBhIHN5bWJvbCwgZWxzZSBgZmFsc2VgLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmlzU3ltYm9sKFN5bWJvbC5pdGVyYXRvcik7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc1N5bWJvbCgnYWJjJyk7XG4gKiAvLyA9PiBmYWxzZVxuICovXG5mdW5jdGlvbiBpc1N5bWJvbCh2YWx1ZSkge1xuICByZXR1cm4gdHlwZW9mIHZhbHVlID09ICdzeW1ib2wnIHx8XG4gICAgKGlzT2JqZWN0TGlrZSh2YWx1ZSkgJiYgb2JqZWN0VG9TdHJpbmcuY2FsbCh2YWx1ZSkgPT0gc3ltYm9sVGFnKTtcbn1cblxuLyoqXG4gKiBDb252ZXJ0cyBgdmFsdWVgIHRvIGEgbnVtYmVyLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgNC4wLjBcbiAqIEBjYXRlZ29yeSBMYW5nXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBwcm9jZXNzLlxuICogQHJldHVybnMge251bWJlcn0gUmV0dXJucyB0aGUgbnVtYmVyLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLnRvTnVtYmVyKDMuMik7XG4gKiAvLyA9PiAzLjJcbiAqXG4gKiBfLnRvTnVtYmVyKE51bWJlci5NSU5fVkFMVUUpO1xuICogLy8gPT4gNWUtMzI0XG4gKlxuICogXy50b051bWJlcihJbmZpbml0eSk7XG4gKiAvLyA9PiBJbmZpbml0eVxuICpcbiAqIF8udG9OdW1iZXIoJzMuMicpO1xuICogLy8gPT4gMy4yXG4gKi9cbmZ1bmN0aW9uIHRvTnVtYmVyKHZhbHVlKSB7XG4gIGlmICh0eXBlb2YgdmFsdWUgPT0gJ251bWJlcicpIHtcbiAgICByZXR1cm4gdmFsdWU7XG4gIH1cbiAgaWYgKGlzU3ltYm9sKHZhbHVlKSkge1xuICAgIHJldHVybiBOQU47XG4gIH1cbiAgaWYgKGlzT2JqZWN0KHZhbHVlKSkge1xuICAgIHZhciBvdGhlciA9IHR5cGVvZiB2YWx1ZS52YWx1ZU9mID09ICdmdW5jdGlvbicgPyB2YWx1ZS52YWx1ZU9mKCkgOiB2YWx1ZTtcbiAgICB2YWx1ZSA9IGlzT2JqZWN0KG90aGVyKSA/IChvdGhlciArICcnKSA6IG90aGVyO1xuICB9XG4gIGlmICh0eXBlb2YgdmFsdWUgIT0gJ3N0cmluZycpIHtcbiAgICByZXR1cm4gdmFsdWUgPT09IDAgPyB2YWx1ZSA6ICt2YWx1ZTtcbiAgfVxuICB2YWx1ZSA9IHZhbHVlLnJlcGxhY2UocmVUcmltLCAnJyk7XG4gIHZhciBpc0JpbmFyeSA9IHJlSXNCaW5hcnkudGVzdCh2YWx1ZSk7XG4gIHJldHVybiAoaXNCaW5hcnkgfHwgcmVJc09jdGFsLnRlc3QodmFsdWUpKVxuICAgID8gZnJlZVBhcnNlSW50KHZhbHVlLnNsaWNlKDIpLCBpc0JpbmFyeSA/IDIgOiA4KVxuICAgIDogKHJlSXNCYWRIZXgudGVzdCh2YWx1ZSkgPyBOQU4gOiArdmFsdWUpO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IHRocm90dGxlO1xuIiwidmFyIGc7XHJcblxyXG4vLyBUaGlzIHdvcmtzIGluIG5vbi1zdHJpY3QgbW9kZVxyXG5nID0gKGZ1bmN0aW9uKCkge1xyXG5cdHJldHVybiB0aGlzO1xyXG59KSgpO1xyXG5cclxudHJ5IHtcclxuXHQvLyBUaGlzIHdvcmtzIGlmIGV2YWwgaXMgYWxsb3dlZCAoc2VlIENTUClcclxuXHRnID0gZyB8fCBGdW5jdGlvbihcInJldHVybiB0aGlzXCIpKCkgfHwgKDEsIGV2YWwpKFwidGhpc1wiKTtcclxufSBjYXRjaCAoZSkge1xyXG5cdC8vIFRoaXMgd29ya3MgaWYgdGhlIHdpbmRvdyByZWZlcmVuY2UgaXMgYXZhaWxhYmxlXHJcblx0aWYgKHR5cGVvZiB3aW5kb3cgPT09IFwib2JqZWN0XCIpIGcgPSB3aW5kb3c7XHJcbn1cclxuXHJcbi8vIGcgY2FuIHN0aWxsIGJlIHVuZGVmaW5lZCwgYnV0IG5vdGhpbmcgdG8gZG8gYWJvdXQgaXQuLi5cclxuLy8gV2UgcmV0dXJuIHVuZGVmaW5lZCwgaW5zdGVhZCBvZiBub3RoaW5nIGhlcmUsIHNvIGl0J3NcclxuLy8gZWFzaWVyIHRvIGhhbmRsZSB0aGlzIGNhc2UuIGlmKCFnbG9iYWwpIHsgLi4ufVxyXG5cclxubW9kdWxlLmV4cG9ydHMgPSBnO1xyXG4iLCJcInVzZSBzdHJpY3RcIjtcbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwgeyB2YWx1ZTogdHJ1ZSB9KTtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuY29uc3Qgc2Nyb2xsX3N5bmNfMSA9IHJlcXVpcmUoXCIuL3Njcm9sbC1zeW5jXCIpO1xuY2xhc3MgQWN0aXZlTGluZU1hcmtlciB7XG4gICAgb25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uKGxpbmUpIHtcbiAgICAgICAgY29uc3QgeyBwcmV2aW91cyB9ID0gc2Nyb2xsX3N5bmNfMS5nZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUobGluZSk7XG4gICAgICAgIHRoaXMuX3VwZGF0ZShwcmV2aW91cyAmJiBwcmV2aW91cy5lbGVtZW50KTtcbiAgICB9XG4gICAgX3VwZGF0ZShiZWZvcmUpIHtcbiAgICAgICAgdGhpcy5fdW5tYXJrQWN0aXZlRWxlbWVudCh0aGlzLl9jdXJyZW50KTtcbiAgICAgICAgdGhpcy5fbWFya0FjdGl2ZUVsZW1lbnQoYmVmb3JlKTtcbiAgICAgICAgdGhpcy5fY3VycmVudCA9IGJlZm9yZTtcbiAgICB9XG4gICAgX3VubWFya0FjdGl2ZUVsZW1lbnQoZWxlbWVudCkge1xuICAgICAgICBpZiAoIWVsZW1lbnQpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBlbGVtZW50LmNsYXNzTmFtZSA9IGVsZW1lbnQuY2xhc3NOYW1lLnJlcGxhY2UoL1xcYmNvZGUtYWN0aXZlLWxpbmVcXGIvZywgJycpO1xuICAgIH1cbiAgICBfbWFya0FjdGl2ZUVsZW1lbnQoZWxlbWVudCkge1xuICAgICAgICBpZiAoIWVsZW1lbnQpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBlbGVtZW50LmNsYXNzTmFtZSArPSAnIGNvZGUtYWN0aXZlLWxpbmUnO1xuICAgIH1cbn1cbmV4cG9ydHMuQWN0aXZlTGluZU1hcmtlciA9IEFjdGl2ZUxpbmVNYXJrZXI7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuZnVuY3Rpb24gb25jZURvY3VtZW50TG9hZGVkKGYpIHtcbiAgICBpZiAoZG9jdW1lbnQucmVhZHlTdGF0ZSA9PT0gJ2xvYWRpbmcnIHx8IGRvY3VtZW50LnJlYWR5U3RhdGUgPT09ICd1bmluaXRpYWxpemVkJykge1xuICAgICAgICBkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdET01Db250ZW50TG9hZGVkJywgZik7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBmKCk7XG4gICAgfVxufVxuZXhwb3J0cy5vbmNlRG9jdW1lbnRMb2FkZWQgPSBvbmNlRG9jdW1lbnRMb2FkZWQ7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuY29uc3QgYWN0aXZlTGluZU1hcmtlcl8xID0gcmVxdWlyZShcIi4vYWN0aXZlTGluZU1hcmtlclwiKTtcbmNvbnN0IGV2ZW50c18xID0gcmVxdWlyZShcIi4vZXZlbnRzXCIpO1xuY29uc3QgbWVzc2FnaW5nXzEgPSByZXF1aXJlKFwiLi9tZXNzYWdpbmdcIik7XG5jb25zdCBzY3JvbGxfc3luY18xID0gcmVxdWlyZShcIi4vc2Nyb2xsLXN5bmNcIik7XG5jb25zdCBzZXR0aW5nc18xID0gcmVxdWlyZShcIi4vc2V0dGluZ3NcIik7XG5jb25zdCB0aHJvdHRsZSA9IHJlcXVpcmUoXCJsb2Rhc2gudGhyb3R0bGVcIik7XG5sZXQgc2Nyb2xsRGlzYWJsZWQgPSB0cnVlO1xuY29uc3QgbWFya2VyID0gbmV3IGFjdGl2ZUxpbmVNYXJrZXJfMS5BY3RpdmVMaW5lTWFya2VyKCk7XG5jb25zdCBzZXR0aW5ncyA9IHNldHRpbmdzXzEuZ2V0U2V0dGluZ3MoKTtcbmNvbnN0IHZzY29kZSA9IGFjcXVpcmVWc0NvZGVBcGkoKTtcbi8vIFNldCBWUyBDb2RlIHN0YXRlXG5sZXQgc3RhdGUgPSBzZXR0aW5nc18xLmdldERhdGEoJ2RhdGEtc3RhdGUnKTtcbnZzY29kZS5zZXRTdGF0ZShzdGF0ZSk7XG5jb25zdCBtZXNzYWdpbmcgPSBtZXNzYWdpbmdfMS5jcmVhdGVQb3N0ZXJGb3JWc0NvZGUodnNjb2RlKTtcbndpbmRvdy5jc3BBbGVydGVyLnNldFBvc3RlcihtZXNzYWdpbmcpO1xud2luZG93LnN0eWxlTG9hZGluZ01vbml0b3Iuc2V0UG9zdGVyKG1lc3NhZ2luZyk7XG53aW5kb3cub25sb2FkID0gKCkgPT4ge1xuICAgIHVwZGF0ZUltYWdlU2l6ZXMoKTtcbn07XG5ldmVudHNfMS5vbmNlRG9jdW1lbnRMb2FkZWQoKCkgPT4ge1xuICAgIGlmIChzZXR0aW5ncy5zY3JvbGxQcmV2aWV3V2l0aEVkaXRvcikge1xuICAgICAgICBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgICAgICAgIC8vIFRyeSB0byBzY3JvbGwgdG8gZnJhZ21lbnQgaWYgYXZhaWxhYmxlXG4gICAgICAgICAgICBpZiAoc3RhdGUuZnJhZ21lbnQpIHtcbiAgICAgICAgICAgICAgICBjb25zdCBlbGVtZW50ID0gc2Nyb2xsX3N5bmNfMS5nZXRMaW5lRWxlbWVudEZvckZyYWdtZW50KHN0YXRlLmZyYWdtZW50KTtcbiAgICAgICAgICAgICAgICBpZiAoZWxlbWVudCkge1xuICAgICAgICAgICAgICAgICAgICBzY3JvbGxEaXNhYmxlZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgIHNjcm9sbF9zeW5jXzEuc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGVsZW1lbnQubGluZSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgY29uc3QgaW5pdGlhbExpbmUgPSArc2V0dGluZ3MubGluZTtcbiAgICAgICAgICAgICAgICBpZiAoIWlzTmFOKGluaXRpYWxMaW5lKSkge1xuICAgICAgICAgICAgICAgICAgICBzY3JvbGxEaXNhYmxlZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgIHNjcm9sbF9zeW5jXzEuc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGluaXRpYWxMaW5lKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH0sIDApO1xuICAgIH1cbn0pO1xuY29uc3Qgb25VcGRhdGVWaWV3ID0gKCgpID0+IHtcbiAgICBjb25zdCBkb1Njcm9sbCA9IHRocm90dGxlKChsaW5lKSA9PiB7XG4gICAgICAgIHNjcm9sbERpc2FibGVkID0gdHJ1ZTtcbiAgICAgICAgc2Nyb2xsX3N5bmNfMS5zY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUobGluZSk7XG4gICAgfSwgNTApO1xuICAgIHJldHVybiAobGluZSwgc2V0dGluZ3MpID0+IHtcbiAgICAgICAgaWYgKCFpc05hTihsaW5lKSkge1xuICAgICAgICAgICAgc2V0dGluZ3MubGluZSA9IGxpbmU7XG4gICAgICAgICAgICBkb1Njcm9sbChsaW5lKTtcbiAgICAgICAgfVxuICAgIH07XG59KSgpO1xubGV0IHVwZGF0ZUltYWdlU2l6ZXMgPSB0aHJvdHRsZSgoKSA9PiB7XG4gICAgY29uc3QgaW1hZ2VJbmZvID0gW107XG4gICAgbGV0IGltYWdlcyA9IGRvY3VtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCdpbWcnKTtcbiAgICBpZiAoaW1hZ2VzKSB7XG4gICAgICAgIGxldCBpO1xuICAgICAgICBmb3IgKGkgPSAwOyBpIDwgaW1hZ2VzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBjb25zdCBpbWcgPSBpbWFnZXNbaV07XG4gICAgICAgICAgICBpZiAoaW1nLmNsYXNzTGlzdC5jb250YWlucygnbG9hZGluZycpKSB7XG4gICAgICAgICAgICAgICAgaW1nLmNsYXNzTGlzdC5yZW1vdmUoJ2xvYWRpbmcnKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGltYWdlSW5mby5wdXNoKHtcbiAgICAgICAgICAgICAgICBpZDogaW1nLmlkLFxuICAgICAgICAgICAgICAgIGhlaWdodDogaW1nLmhlaWdodCxcbiAgICAgICAgICAgICAgICB3aWR0aDogaW1nLndpZHRoXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgICAgICBtZXNzYWdpbmcucG9zdE1lc3NhZ2UoJ2NhY2hlSW1hZ2VTaXplcycsIGltYWdlSW5mbyk7XG4gICAgfVxufSwgNTApO1xud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ3Jlc2l6ZScsICgpID0+IHtcbiAgICBzY3JvbGxEaXNhYmxlZCA9IHRydWU7XG4gICAgdXBkYXRlSW1hZ2VTaXplcygpO1xufSwgdHJ1ZSk7XG53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbWVzc2FnZScsIGV2ZW50ID0+IHtcbiAgICBpZiAoZXZlbnQuZGF0YS5zb3VyY2UgIT09IHNldHRpbmdzLnNvdXJjZSkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIHN3aXRjaCAoZXZlbnQuZGF0YS50eXBlKSB7XG4gICAgICAgIGNhc2UgJ29uRGlkQ2hhbmdlVGV4dEVkaXRvclNlbGVjdGlvbic6XG4gICAgICAgICAgICBtYXJrZXIub25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uKGV2ZW50LmRhdGEubGluZSk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSAndXBkYXRlVmlldyc6XG4gICAgICAgICAgICBvblVwZGF0ZVZpZXcoZXZlbnQuZGF0YS5saW5lLCBzZXR0aW5ncyk7XG4gICAgICAgICAgICBicmVhaztcbiAgICB9XG59LCBmYWxzZSk7XG5kb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdkYmxjbGljaycsIGV2ZW50ID0+IHtcbiAgICBpZiAoIXNldHRpbmdzLmRvdWJsZUNsaWNrVG9Td2l0Y2hUb0VkaXRvcikge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIC8vIElnbm9yZSBjbGlja3Mgb24gbGlua3NcbiAgICBmb3IgKGxldCBub2RlID0gZXZlbnQudGFyZ2V0OyBub2RlOyBub2RlID0gbm9kZS5wYXJlbnROb2RlKSB7XG4gICAgICAgIGlmIChub2RlLnRhZ05hbWUgPT09ICdBJykge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgfVxuICAgIGNvbnN0IG9mZnNldCA9IGV2ZW50LnBhZ2VZO1xuICAgIGNvbnN0IGxpbmUgPSBzY3JvbGxfc3luY18xLmdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0KG9mZnNldCk7XG4gICAgaWYgKHR5cGVvZiBsaW5lID09PSAnbnVtYmVyJyAmJiAhaXNOYU4obGluZSkpIHtcbiAgICAgICAgbWVzc2FnaW5nLnBvc3RNZXNzYWdlKCdkaWRDbGljaycsIHsgbGluZTogTWF0aC5mbG9vcihsaW5lKSB9KTtcbiAgICB9XG59KTtcbmRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgZXZlbnQgPT4ge1xuICAgIGlmICghZXZlbnQpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBsZXQgbm9kZSA9IGV2ZW50LnRhcmdldDtcbiAgICB3aGlsZSAobm9kZSkge1xuICAgICAgICBpZiAobm9kZS50YWdOYW1lICYmIG5vZGUudGFnTmFtZSA9PT0gJ0EnICYmIG5vZGUuaHJlZikge1xuICAgICAgICAgICAgaWYgKG5vZGUuZ2V0QXR0cmlidXRlKCdocmVmJykuc3RhcnRzV2l0aCgnIycpKSB7XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBpZiAobm9kZS5ocmVmLnN0YXJ0c1dpdGgoJ2ZpbGU6Ly8nKSB8fCBub2RlLmhyZWYuc3RhcnRzV2l0aCgndnNjb2RlLXJlc291cmNlOicpIHx8IG5vZGUuaHJlZi5zdGFydHNXaXRoKHNldHRpbmdzLndlYnZpZXdSZXNvdXJjZVJvb3QpKSB7XG4gICAgICAgICAgICAgICAgY29uc3QgW3BhdGgsIGZyYWdtZW50XSA9IG5vZGUuaHJlZi5yZXBsYWNlKC9eKGZpbGU6XFwvXFwvfHZzY29kZS1yZXNvdXJjZTopL2ksICcnKS5yZXBsYWNlKG5ldyBSZWdFeHAoYF4ke2VzY2FwZVJlZ0V4cChzZXR0aW5ncy53ZWJ2aWV3UmVzb3VyY2VSb290KX1gKSkuc3BsaXQoJyMnKTtcbiAgICAgICAgICAgICAgICBtZXNzYWdpbmcucG9zdE1lc3NhZ2UoJ2NsaWNrTGluaycsIHsgcGF0aCwgZnJhZ21lbnQgfSk7XG4gICAgICAgICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgICAgICAgICBldmVudC5zdG9wUHJvcGFnYXRpb24oKTtcbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICB9XG4gICAgICAgIG5vZGUgPSBub2RlLnBhcmVudE5vZGU7XG4gICAgfVxufSwgdHJ1ZSk7XG5pZiAoc2V0dGluZ3Muc2Nyb2xsRWRpdG9yV2l0aFByZXZpZXcpIHtcbiAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignc2Nyb2xsJywgdGhyb3R0bGUoKCkgPT4ge1xuICAgICAgICBpZiAoc2Nyb2xsRGlzYWJsZWQpIHtcbiAgICAgICAgICAgIHNjcm9sbERpc2FibGVkID0gZmFsc2U7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBjb25zdCBsaW5lID0gc2Nyb2xsX3N5bmNfMS5nZXRFZGl0b3JMaW5lTnVtYmVyRm9yUGFnZU9mZnNldCh3aW5kb3cuc2Nyb2xsWSk7XG4gICAgICAgICAgICBpZiAodHlwZW9mIGxpbmUgPT09ICdudW1iZXInICYmICFpc05hTihsaW5lKSkge1xuICAgICAgICAgICAgICAgIG1lc3NhZ2luZy5wb3N0TWVzc2FnZSgncmV2ZWFsTGluZScsIHsgbGluZSB9KTtcbiAgICAgICAgICAgICAgICBzdGF0ZS5saW5lID0gbGluZTtcbiAgICAgICAgICAgICAgICB2c2NvZGUuc2V0U3RhdGUoc3RhdGUpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfSwgNTApKTtcbn1cbmZ1bmN0aW9uIGVzY2FwZVJlZ0V4cCh0ZXh0KSB7XG4gICAgcmV0dXJuIHRleHQucmVwbGFjZSgvWy1bXFxde30oKSorPy4sXFxcXF4kfCNcXHNdL2csICdcXFxcJCYnKTtcbn1cbiIsIlwidXNlIHN0cmljdFwiO1xuLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgXCJfX2VzTW9kdWxlXCIsIHsgdmFsdWU6IHRydWUgfSk7XG5jb25zdCBzZXR0aW5nc18xID0gcmVxdWlyZShcIi4vc2V0dGluZ3NcIik7XG5leHBvcnRzLmNyZWF0ZVBvc3RlckZvclZzQ29kZSA9ICh2c2NvZGUpID0+IHtcbiAgICByZXR1cm4gbmV3IGNsYXNzIHtcbiAgICAgICAgcG9zdE1lc3NhZ2UodHlwZSwgYm9keSkge1xuICAgICAgICAgICAgdnNjb2RlLnBvc3RNZXNzYWdlKHtcbiAgICAgICAgICAgICAgICB0eXBlLFxuICAgICAgICAgICAgICAgIHNvdXJjZTogc2V0dGluZ3NfMS5nZXRTZXR0aW5ncygpLnNvdXJjZSxcbiAgICAgICAgICAgICAgICBib2R5XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgIH07XG59O1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwgeyB2YWx1ZTogdHJ1ZSB9KTtcbmNvbnN0IHNldHRpbmdzXzEgPSByZXF1aXJlKFwiLi9zZXR0aW5nc1wiKTtcbmZ1bmN0aW9uIGNsYW1wKG1pbiwgbWF4LCB2YWx1ZSkge1xuICAgIHJldHVybiBNYXRoLm1pbihtYXgsIE1hdGgubWF4KG1pbiwgdmFsdWUpKTtcbn1cbmZ1bmN0aW9uIGNsYW1wTGluZShsaW5lKSB7XG4gICAgcmV0dXJuIGNsYW1wKDAsIHNldHRpbmdzXzEuZ2V0U2V0dGluZ3MoKS5saW5lQ291bnQgLSAxLCBsaW5lKTtcbn1cbmNvbnN0IGdldENvZGVMaW5lRWxlbWVudHMgPSAoKCkgPT4ge1xuICAgIGxldCBlbGVtZW50cztcbiAgICByZXR1cm4gKCkgPT4ge1xuICAgICAgICBpZiAoIWVsZW1lbnRzKSB7XG4gICAgICAgICAgICBlbGVtZW50cyA9IFt7IGVsZW1lbnQ6IGRvY3VtZW50LmJvZHksIGxpbmU6IDAgfV07XG4gICAgICAgICAgICBmb3IgKGNvbnN0IGVsZW1lbnQgb2YgZG9jdW1lbnQuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZSgnY29kZS1saW5lJykpIHtcbiAgICAgICAgICAgICAgICBjb25zdCBsaW5lID0gK2VsZW1lbnQuZ2V0QXR0cmlidXRlKCdkYXRhLWxpbmUnKTtcbiAgICAgICAgICAgICAgICBpZiAoIWlzTmFOKGxpbmUpKSB7XG4gICAgICAgICAgICAgICAgICAgIGVsZW1lbnRzLnB1c2goeyBlbGVtZW50OiBlbGVtZW50LCBsaW5lIH0pO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gZWxlbWVudHM7XG4gICAgfTtcbn0pKCk7XG4vKipcbiAqIEZpbmQgdGhlIGh0bWwgZWxlbWVudHMgdGhhdCBtYXAgdG8gYSBzcGVjaWZpYyB0YXJnZXQgbGluZSBpbiB0aGUgZWRpdG9yLlxuICpcbiAqIElmIGFuIGV4YWN0IG1hdGNoLCByZXR1cm5zIGEgc2luZ2xlIGVsZW1lbnQuIElmIHRoZSBsaW5lIGlzIGJldHdlZW4gZWxlbWVudHMsXG4gKiByZXR1cm5zIHRoZSBlbGVtZW50IHByaW9yIHRvIGFuZCB0aGUgZWxlbWVudCBhZnRlciB0aGUgZ2l2ZW4gbGluZS5cbiAqL1xuZnVuY3Rpb24gZ2V0RWxlbWVudHNGb3JTb3VyY2VMaW5lKHRhcmdldExpbmUpIHtcbiAgICBjb25zdCBsaW5lTnVtYmVyID0gTWF0aC5mbG9vcih0YXJnZXRMaW5lKTtcbiAgICBjb25zdCBsaW5lcyA9IGdldENvZGVMaW5lRWxlbWVudHMoKTtcbiAgICBsZXQgcHJldmlvdXMgPSBsaW5lc1swXSB8fCBudWxsO1xuICAgIGZvciAoY29uc3QgZW50cnkgb2YgbGluZXMpIHtcbiAgICAgICAgaWYgKGVudHJ5LmxpbmUgPT09IGxpbmVOdW1iZXIpIHtcbiAgICAgICAgICAgIHJldHVybiB7IHByZXZpb3VzOiBlbnRyeSwgbmV4dDogdW5kZWZpbmVkIH07XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAoZW50cnkubGluZSA+IGxpbmVOdW1iZXIpIHtcbiAgICAgICAgICAgIHJldHVybiB7IHByZXZpb3VzLCBuZXh0OiBlbnRyeSB9O1xuICAgICAgICB9XG4gICAgICAgIHByZXZpb3VzID0gZW50cnk7XG4gICAgfVxuICAgIHJldHVybiB7IHByZXZpb3VzIH07XG59XG5leHBvcnRzLmdldEVsZW1lbnRzRm9yU291cmNlTGluZSA9IGdldEVsZW1lbnRzRm9yU291cmNlTGluZTtcbi8qKlxuICogRmluZCB0aGUgaHRtbCBlbGVtZW50cyB0aGF0IGFyZSBhdCBhIHNwZWNpZmljIHBpeGVsIG9mZnNldCBvbiB0aGUgcGFnZS5cbiAqL1xuZnVuY3Rpb24gZ2V0TGluZUVsZW1lbnRzQXRQYWdlT2Zmc2V0KG9mZnNldCkge1xuICAgIGNvbnN0IGxpbmVzID0gZ2V0Q29kZUxpbmVFbGVtZW50cygpO1xuICAgIGNvbnN0IHBvc2l0aW9uID0gb2Zmc2V0IC0gd2luZG93LnNjcm9sbFk7XG4gICAgbGV0IGxvID0gLTE7XG4gICAgbGV0IGhpID0gbGluZXMubGVuZ3RoIC0gMTtcbiAgICB3aGlsZSAobG8gKyAxIDwgaGkpIHtcbiAgICAgICAgY29uc3QgbWlkID0gTWF0aC5mbG9vcigobG8gKyBoaSkgLyAyKTtcbiAgICAgICAgY29uc3QgYm91bmRzID0gbGluZXNbbWlkXS5lbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICAgICAgICBpZiAoYm91bmRzLnRvcCArIGJvdW5kcy5oZWlnaHQgPj0gcG9zaXRpb24pIHtcbiAgICAgICAgICAgIGhpID0gbWlkO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgbG8gPSBtaWQ7XG4gICAgICAgIH1cbiAgICB9XG4gICAgY29uc3QgaGlFbGVtZW50ID0gbGluZXNbaGldO1xuICAgIGNvbnN0IGhpQm91bmRzID0gaGlFbGVtZW50LmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG4gICAgaWYgKGhpID49IDEgJiYgaGlCb3VuZHMudG9wID4gcG9zaXRpb24pIHtcbiAgICAgICAgY29uc3QgbG9FbGVtZW50ID0gbGluZXNbbG9dO1xuICAgICAgICByZXR1cm4geyBwcmV2aW91czogbG9FbGVtZW50LCBuZXh0OiBoaUVsZW1lbnQgfTtcbiAgICB9XG4gICAgcmV0dXJuIHsgcHJldmlvdXM6IGhpRWxlbWVudCB9O1xufVxuZXhwb3J0cy5nZXRMaW5lRWxlbWVudHNBdFBhZ2VPZmZzZXQgPSBnZXRMaW5lRWxlbWVudHNBdFBhZ2VPZmZzZXQ7XG4vKipcbiAqIEF0dGVtcHQgdG8gcmV2ZWFsIHRoZSBlbGVtZW50IGZvciBhIHNvdXJjZSBsaW5lIGluIHRoZSBlZGl0b3IuXG4gKi9cbmZ1bmN0aW9uIHNjcm9sbFRvUmV2ZWFsU291cmNlTGluZShsaW5lKSB7XG4gICAgaWYgKCFzZXR0aW5nc18xLmdldFNldHRpbmdzKCkuc2Nyb2xsUHJldmlld1dpdGhFZGl0b3IpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBpZiAobGluZSA8PSAwKSB7XG4gICAgICAgIHdpbmRvdy5zY3JvbGwod2luZG93LnNjcm9sbFgsIDApO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IHsgcHJldmlvdXMsIG5leHQgfSA9IGdldEVsZW1lbnRzRm9yU291cmNlTGluZShsaW5lKTtcbiAgICBpZiAoIXByZXZpb3VzKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgbGV0IHNjcm9sbFRvID0gMDtcbiAgICBjb25zdCByZWN0ID0gcHJldmlvdXMuZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcbiAgICBjb25zdCBwcmV2aW91c1RvcCA9IHJlY3QudG9wO1xuICAgIGlmIChuZXh0ICYmIG5leHQubGluZSAhPT0gcHJldmlvdXMubGluZSkge1xuICAgICAgICAvLyBCZXR3ZWVuIHR3byBlbGVtZW50cy4gR28gdG8gcGVyY2VudGFnZSBvZmZzZXQgYmV0d2VlbiB0aGVtLlxuICAgICAgICBjb25zdCBiZXR3ZWVuUHJvZ3Jlc3MgPSAobGluZSAtIHByZXZpb3VzLmxpbmUpIC8gKG5leHQubGluZSAtIHByZXZpb3VzLmxpbmUpO1xuICAgICAgICBjb25zdCBlbGVtZW50T2Zmc2V0ID0gbmV4dC5lbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLnRvcCAtIHByZXZpb3VzVG9wO1xuICAgICAgICBzY3JvbGxUbyA9IHByZXZpb3VzVG9wICsgYmV0d2VlblByb2dyZXNzICogZWxlbWVudE9mZnNldDtcbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICAgIGNvbnN0IHByb2dyZXNzSW5FbGVtZW50ID0gbGluZSAtIE1hdGguZmxvb3IobGluZSk7XG4gICAgICAgIHNjcm9sbFRvID0gcHJldmlvdXNUb3AgKyAocmVjdC5oZWlnaHQgKiBwcm9ncmVzc0luRWxlbWVudCk7XG4gICAgfVxuICAgIHdpbmRvdy5zY3JvbGwod2luZG93LnNjcm9sbFgsIE1hdGgubWF4KDEsIHdpbmRvdy5zY3JvbGxZICsgc2Nyb2xsVG8pKTtcbn1cbmV4cG9ydHMuc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lID0gc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lO1xuZnVuY3Rpb24gZ2V0RWRpdG9yTGluZU51bWJlckZvclBhZ2VPZmZzZXQob2Zmc2V0KSB7XG4gICAgY29uc3QgeyBwcmV2aW91cywgbmV4dCB9ID0gZ2V0TGluZUVsZW1lbnRzQXRQYWdlT2Zmc2V0KG9mZnNldCk7XG4gICAgaWYgKHByZXZpb3VzKSB7XG4gICAgICAgIGNvbnN0IHByZXZpb3VzQm91bmRzID0gcHJldmlvdXMuZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcbiAgICAgICAgY29uc3Qgb2Zmc2V0RnJvbVByZXZpb3VzID0gKG9mZnNldCAtIHdpbmRvdy5zY3JvbGxZIC0gcHJldmlvdXNCb3VuZHMudG9wKTtcbiAgICAgICAgaWYgKG5leHQpIHtcbiAgICAgICAgICAgIGNvbnN0IHByb2dyZXNzQmV0d2VlbkVsZW1lbnRzID0gb2Zmc2V0RnJvbVByZXZpb3VzIC8gKG5leHQuZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS50b3AgLSBwcmV2aW91c0JvdW5kcy50b3ApO1xuICAgICAgICAgICAgY29uc3QgbGluZSA9IHByZXZpb3VzLmxpbmUgKyBwcm9ncmVzc0JldHdlZW5FbGVtZW50cyAqIChuZXh0LmxpbmUgLSBwcmV2aW91cy5saW5lKTtcbiAgICAgICAgICAgIHJldHVybiBjbGFtcExpbmUobGluZSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBjb25zdCBwcm9ncmVzc1dpdGhpbkVsZW1lbnQgPSBvZmZzZXRGcm9tUHJldmlvdXMgLyAocHJldmlvdXNCb3VuZHMuaGVpZ2h0KTtcbiAgICAgICAgICAgIGNvbnN0IGxpbmUgPSBwcmV2aW91cy5saW5lICsgcHJvZ3Jlc3NXaXRoaW5FbGVtZW50O1xuICAgICAgICAgICAgcmV0dXJuIGNsYW1wTGluZShsaW5lKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gbnVsbDtcbn1cbmV4cG9ydHMuZ2V0RWRpdG9yTGluZU51bWJlckZvclBhZ2VPZmZzZXQgPSBnZXRFZGl0b3JMaW5lTnVtYmVyRm9yUGFnZU9mZnNldDtcbi8qKlxuICogVHJ5IHRvIGZpbmQgdGhlIGh0bWwgZWxlbWVudCBieSB1c2luZyBhIGZyYWdtZW50IGlkXG4gKi9cbmZ1bmN0aW9uIGdldExpbmVFbGVtZW50Rm9yRnJhZ21lbnQoZnJhZ21lbnQpIHtcbiAgICByZXR1cm4gZ2V0Q29kZUxpbmVFbGVtZW50cygpLmZpbmQoKGVsZW1lbnQpID0+IHtcbiAgICAgICAgcmV0dXJuIGVsZW1lbnQuZWxlbWVudC5pZCA9PT0gZnJhZ21lbnQ7XG4gICAgfSk7XG59XG5leHBvcnRzLmdldExpbmVFbGVtZW50Rm9yRnJhZ21lbnQgPSBnZXRMaW5lRWxlbWVudEZvckZyYWdtZW50O1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwgeyB2YWx1ZTogdHJ1ZSB9KTtcbmxldCBjYWNoZWRTZXR0aW5ncyA9IHVuZGVmaW5lZDtcbmZ1bmN0aW9uIGdldERhdGEoa2V5KSB7XG4gICAgY29uc3QgZWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd2c2NvZGUtbWFya2Rvd24tcHJldmlldy1kYXRhJyk7XG4gICAgaWYgKGVsZW1lbnQpIHtcbiAgICAgICAgY29uc3QgZGF0YSA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKGtleSk7XG4gICAgICAgIGlmIChkYXRhKSB7XG4gICAgICAgICAgICByZXR1cm4gSlNPTi5wYXJzZShkYXRhKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICB0aHJvdyBuZXcgRXJyb3IoYENvdWxkIG5vdCBsb2FkIGRhdGEgZm9yICR7a2V5fWApO1xufVxuZXhwb3J0cy5nZXREYXRhID0gZ2V0RGF0YTtcbmZ1bmN0aW9uIGdldFNldHRpbmdzKCkge1xuICAgIGlmIChjYWNoZWRTZXR0aW5ncykge1xuICAgICAgICByZXR1cm4gY2FjaGVkU2V0dGluZ3M7XG4gICAgfVxuICAgIGNhY2hlZFNldHRpbmdzID0gZ2V0RGF0YSgnZGF0YS1zZXR0aW5ncycpO1xuICAgIGlmIChjYWNoZWRTZXR0aW5ncykge1xuICAgICAgICByZXR1cm4gY2FjaGVkU2V0dGluZ3M7XG4gICAgfVxuICAgIHRocm93IG5ldyBFcnJvcignQ291bGQgbm90IGxvYWQgc2V0dGluZ3MnKTtcbn1cbmV4cG9ydHMuZ2V0U2V0dGluZ3MgPSBnZXRTZXR0aW5ncztcbiJdLCJzb3VyY2VSb290IjoiIn0= \ No newline at end of file +!function(e){var t={};function n(o){if(t[o])return t[o].exports;var i=t[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:o})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=11)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});let o=void 0;function i(e){const t=document.getElementById("vscode-markdown-preview-data");if(t){const n=t.getAttribute(e);if(n)return JSON.parse(n)}throw new Error(`Could not load data for ${e}`)}t.getData=i,t.getSettings=function(){if(o)return o;if(o=i("data-settings"))return o;throw new Error("Could not load settings")}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(0);function i(e){return t=0,n=o.getSettings().lineCount-1,i=e,Math.min(n,Math.max(t,i));var t,n,i}const r=(()=>{let e;return()=>{if(!e){e=[{element:document.body,line:0}];for(const t of document.getElementsByClassName("code-line")){const n=+t.getAttribute("data-line");isNaN(n)||e.push({element:t,line:n})}}return e}})();function s(e){const t=Math.floor(e),n=r();let o=n[0]||null;for(const e of n){if(e.line===t)return{previous:e,next:void 0};if(e.line>t)return{previous:o,next:e};o=e}return{previous:o}}function c(e){const t=r(),n=e-window.scrollY;let o=-1,i=t.length-1;for(;o+1=n?i=e:o=e}const s=t[i],c=s.element.getBoundingClientRect();if(i>=1&&c.top>n){return{previous:t[o],next:s}}return{previous:s}}t.getElementsForSourceLine=s,t.getLineElementsAtPageOffset=c,t.scrollToRevealSourceLine=function(e){if(!o.getSettings().scrollPreviewWithEditor)return;if(e<=0)return void window.scroll(window.scrollX,0);const{previous:t,next:n}=s(e);if(!t)return;let i=0;const r=t.element.getBoundingClientRect(),c=r.top;if(n&&n.line!==t.line)i=c+(e-t.line)/(n.line-t.line)*(n.element.getBoundingClientRect().top-c);else{const t=e-Math.floor(e);i=c+r.height*t}window.scroll(window.scrollX,Math.max(1,window.scrollY+i))},t.getEditorLineNumberForPageOffset=function(e){const{previous:t,next:n}=c(e);if(t){const o=t.element.getBoundingClientRect(),r=e-window.scrollY-o.top;if(n){const e=r/(n.element.getBoundingClientRect().top-o.top);return i(t.line+e*(n.line-t.line))}{const e=r/o.height;return i(t.line+e)}}return null},t.getLineElementForFragment=function(e){return r().find(t=>t.element.id===e)}},,,,,function(e,t){var n;n=function(){return this}();try{n=n||Function("return this")()||(0,eval)("this")}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){(function(t){var n="Expected a function",o=NaN,i="[object Symbol]",r=/^\s+|\s+$/g,s=/^[-+]0x[0-9a-f]+$/i,c=/^0b[01]+$/i,a=/^0o[0-7]+$/i,u=parseInt,l="object"==typeof t&&t&&t.Object===Object&&t,d="object"==typeof self&&self&&self.Object===Object&&self,f=l||d||Function("return this")(),g=Object.prototype.toString,p=Math.max,m=Math.min,v=function(){return f.Date.now()};function h(e,t,o){var i,r,s,c,a,u,l=0,d=!1,f=!1,g=!0;if("function"!=typeof e)throw new TypeError(n);function h(t){var n=i,o=r;return i=r=void 0,l=t,c=e.apply(o,n)}function y(e){var n=e-u;return void 0===u||n>=t||n<0||f&&e-l>=s}function E(){var e=v();if(y(e))return L(e);a=setTimeout(E,function(e){var n=t-(e-u);return f?m(n,s-(e-l)):n}(e))}function L(e){return a=void 0,g&&i?h(e):(i=r=void 0,c)}function M(){var e=v(),n=y(e);if(i=arguments,r=this,u=e,n){if(void 0===a)return function(e){return l=e,a=setTimeout(E,t),d?h(e):c}(u);if(f)return a=setTimeout(E,t),h(u)}return void 0===a&&(a=setTimeout(E,t)),c}return t=b(t)||0,w(o)&&(d=!!o.leading,s=(f="maxWait"in o)?p(b(o.maxWait)||0,t):s,g="trailing"in o?!!o.trailing:g),M.cancel=function(){void 0!==a&&clearTimeout(a),l=0,i=u=r=a=void 0},M.flush=function(){return void 0===a?c:L(v())},M}function w(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function b(e){if("number"==typeof e)return e;if(function(e){return"symbol"==typeof e||function(e){return!!e&&"object"==typeof e}(e)&&g.call(e)==i}(e))return o;if(w(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=w(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(r,"");var n=c.test(e);return n||a.test(e)?u(e.slice(2),n?2:8):s.test(e)?o:+e}e.exports=function(e,t,o){var i=!0,r=!0;if("function"!=typeof e)throw new TypeError(n);return w(o)&&(i="leading"in o?!!o.leading:i,r="trailing"in o?!!o.trailing:r),h(e,t,{leading:i,maxWait:t,trailing:r})}}).call(this,n(6))},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(0);t.createPosterForVsCode=(e=>new class{postMessage(t,n){e.postMessage({type:t,source:o.getSettings().source,body:n})}})},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.onceDocumentLoaded=function(e){"loading"===document.readyState||"uninitialized"===document.readyState?document.addEventListener("DOMContentLoaded",e):e()}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(1);t.ActiveLineMarker=class{onDidChangeTextEditorSelection(e){const{previous:t}=o.getElementsForSourceLine(e);this._update(t&&t.element)}_update(e){this._unmarkActiveElement(this._current),this._markActiveElement(e),this._current=e}_unmarkActiveElement(e){e&&(e.className=e.className.replace(/\bcode-active-line\b/g,""))}_markActiveElement(e){e&&(e.className+=" code-active-line")}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(10),i=n(9),r=n(8),s=n(1),c=n(0),a=n(7);let u=!0;const l=new o.ActiveLineMarker,d=c.getSettings(),f=acquireVsCodeApi();let g=c.getData("data-state");f.setState(g);const p=r.createPosterForVsCode(f);window.cspAlerter.setPoster(p),window.styleLoadingMonitor.setPoster(p),window.onload=(()=>{v()}),i.onceDocumentLoaded(()=>{d.scrollPreviewWithEditor&&setTimeout(()=>{if(g.fragment){const e=s.getLineElementForFragment(g.fragment);e&&(u=!0,s.scrollToRevealSourceLine(e.line))}else{const e=+d.line;isNaN(e)||(u=!0,s.scrollToRevealSourceLine(e))}},0)});const m=(()=>{const e=a(e=>{u=!0,s.scrollToRevealSourceLine(e)},50);return(t,n)=>{isNaN(t)||(n.line=t,e(t))}})();let v=a(()=>{const e=[];let t=document.getElementsByTagName("img");if(t){let n;for(n=0;n{u=!0,v()},!0),window.addEventListener("message",e=>{if(e.data.source===d.source)switch(e.data.type){case"onDidChangeTextEditorSelection":l.onDidChangeTextEditorSelection(e.data.line);break;case"updateView":m(e.data.line,d)}},!1),document.addEventListener("dblclick",e=>{if(!d.doubleClickToSwitchToEditor)return;for(let t=e.target;t;t=t.parentNode)if("A"===t.tagName)return;const t=e.pageY,n=s.getEditorLineNumberForPageOffset(t);"number"!=typeof n||isNaN(n)||p.postMessage("didClick",{line:Math.floor(n)})});const h=["http:","https:","mailto:","vscode:","vscode-insiders"];document.addEventListener("click",e=>{if(!e)return;let t=e.target;for(;t;){if(t.tagName&&"A"===t.tagName&&t.href){if(t.getAttribute("href").startsWith("#"))return;if(h.some(e=>t.href.startsWith(e)))return;const n=t.getAttribute("data-href")||t.getAttribute("href");return/^[a-z\-]+:/i.test(n)?void 0:(p.postMessage("openLink",{href:n}),e.preventDefault(),void e.stopPropagation())}t=t.parentNode}},!0),window.addEventListener("scroll",a(()=>{if(u)u=!1;else{const e=s.getEditorLineNumberForPageOffset(window.scrollY);"number"!=typeof e||isNaN(e)||(p.postMessage("revealLine",{line:e}),g.line=e,f.setState(g))}},50))}]); +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvc2V0dGluZ3MudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvc2Nyb2xsLXN5bmMudHMiLCJ3ZWJwYWNrOi8vLyh3ZWJwYWNrKS9idWlsZGluL2dsb2JhbC5qcyIsIndlYnBhY2s6Ly8vLi9ub2RlX21vZHVsZXMvbG9kYXNoLnRocm90dGxlL2luZGV4LmpzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL21lc3NhZ2luZy50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9ldmVudHMudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvYWN0aXZlTGluZU1hcmtlci50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9pbmRleC50cyJdLCJuYW1lcyI6WyJpbnN0YWxsZWRNb2R1bGVzIiwiX193ZWJwYWNrX3JlcXVpcmVfXyIsIm1vZHVsZUlkIiwiZXhwb3J0cyIsIm1vZHVsZSIsImkiLCJsIiwibW9kdWxlcyIsImNhbGwiLCJtIiwiYyIsImQiLCJuYW1lIiwiZ2V0dGVyIiwibyIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwiY29uZmlndXJhYmxlIiwiZW51bWVyYWJsZSIsImdldCIsInIiLCJ2YWx1ZSIsIm4iLCJfX2VzTW9kdWxlIiwib2JqZWN0IiwicHJvcGVydHkiLCJwcm90b3R5cGUiLCJoYXNPd25Qcm9wZXJ0eSIsInAiLCJzIiwiY2FjaGVkU2V0dGluZ3MiLCJ1bmRlZmluZWQiLCJnZXREYXRhIiwia2V5IiwiZWxlbWVudCIsImRvY3VtZW50IiwiZ2V0RWxlbWVudEJ5SWQiLCJkYXRhIiwiZ2V0QXR0cmlidXRlIiwiSlNPTiIsInBhcnNlIiwiRXJyb3IiLCJnZXRTZXR0aW5ncyIsInNldHRpbmdzXzEiLCJjbGFtcExpbmUiLCJsaW5lIiwibWluIiwibWF4IiwibGluZUNvdW50IiwiTWF0aCIsImdldENvZGVMaW5lRWxlbWVudHMiLCJlbGVtZW50cyIsImJvZHkiLCJnZXRFbGVtZW50c0J5Q2xhc3NOYW1lIiwiaXNOYU4iLCJwdXNoIiwiZ2V0RWxlbWVudHNGb3JTb3VyY2VMaW5lIiwidGFyZ2V0TGluZSIsImxpbmVOdW1iZXIiLCJmbG9vciIsImxpbmVzIiwicHJldmlvdXMiLCJlbnRyeSIsIm5leHQiLCJnZXRMaW5lRWxlbWVudHNBdFBhZ2VPZmZzZXQiLCJvZmZzZXQiLCJwb3NpdGlvbiIsIndpbmRvdyIsInNjcm9sbFkiLCJsbyIsImhpIiwibGVuZ3RoIiwibWlkIiwiYm91bmRzIiwiZ2V0Qm91bmRpbmdDbGllbnRSZWN0IiwidG9wIiwiaGVpZ2h0IiwiaGlFbGVtZW50IiwiaGlCb3VuZHMiLCJzY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUiLCJzY3JvbGxQcmV2aWV3V2l0aEVkaXRvciIsInNjcm9sbCIsInNjcm9sbFgiLCJzY3JvbGxUbyIsInJlY3QiLCJwcmV2aW91c1RvcCIsInByb2dyZXNzSW5FbGVtZW50IiwiZ2V0RWRpdG9yTGluZU51bWJlckZvclBhZ2VPZmZzZXQiLCJwcmV2aW91c0JvdW5kcyIsIm9mZnNldEZyb21QcmV2aW91cyIsInByb2dyZXNzQmV0d2VlbkVsZW1lbnRzIiwicHJvZ3Jlc3NXaXRoaW5FbGVtZW50IiwiZ2V0TGluZUVsZW1lbnRGb3JGcmFnbWVudCIsImZyYWdtZW50IiwiZmluZCIsImlkIiwiZyIsInRoaXMiLCJGdW5jdGlvbiIsImV2YWwiLCJlIiwiZ2xvYmFsIiwiRlVOQ19FUlJPUl9URVhUIiwiTkFOIiwic3ltYm9sVGFnIiwicmVUcmltIiwicmVJc0JhZEhleCIsInJlSXNCaW5hcnkiLCJyZUlzT2N0YWwiLCJmcmVlUGFyc2VJbnQiLCJwYXJzZUludCIsImZyZWVHbG9iYWwiLCJmcmVlU2VsZiIsInNlbGYiLCJyb290Iiwib2JqZWN0VG9TdHJpbmciLCJ0b1N0cmluZyIsIm5hdGl2ZU1heCIsIm5hdGl2ZU1pbiIsIm5vdyIsIkRhdGUiLCJkZWJvdW5jZSIsImZ1bmMiLCJ3YWl0Iiwib3B0aW9ucyIsImxhc3RBcmdzIiwibGFzdFRoaXMiLCJtYXhXYWl0IiwicmVzdWx0IiwidGltZXJJZCIsImxhc3RDYWxsVGltZSIsImxhc3RJbnZva2VUaW1lIiwibGVhZGluZyIsIm1heGluZyIsInRyYWlsaW5nIiwiVHlwZUVycm9yIiwiaW52b2tlRnVuYyIsInRpbWUiLCJhcmdzIiwidGhpc0FyZyIsImFwcGx5Iiwic2hvdWxkSW52b2tlIiwidGltZVNpbmNlTGFzdENhbGwiLCJ0aW1lckV4cGlyZWQiLCJ0cmFpbGluZ0VkZ2UiLCJzZXRUaW1lb3V0IiwicmVtYWluaW5nV2FpdCIsImRlYm91bmNlZCIsImlzSW52b2tpbmciLCJhcmd1bWVudHMiLCJsZWFkaW5nRWRnZSIsInRvTnVtYmVyIiwiaXNPYmplY3QiLCJjYW5jZWwiLCJjbGVhclRpbWVvdXQiLCJmbHVzaCIsInR5cGUiLCJpc09iamVjdExpa2UiLCJpc1N5bWJvbCIsIm90aGVyIiwidmFsdWVPZiIsInJlcGxhY2UiLCJpc0JpbmFyeSIsInRlc3QiLCJzbGljZSIsImNyZWF0ZVBvc3RlckZvclZzQ29kZSIsInZzY29kZSIsIltvYmplY3QgT2JqZWN0XSIsInBvc3RNZXNzYWdlIiwic291cmNlIiwib25jZURvY3VtZW50TG9hZGVkIiwiZiIsInJlYWR5U3RhdGUiLCJhZGRFdmVudExpc3RlbmVyIiwic2Nyb2xsX3N5bmNfMSIsIkFjdGl2ZUxpbmVNYXJrZXIiLCJfdXBkYXRlIiwiYmVmb3JlIiwiX3VubWFya0FjdGl2ZUVsZW1lbnQiLCJfY3VycmVudCIsIl9tYXJrQWN0aXZlRWxlbWVudCIsImNsYXNzTmFtZSIsImFjdGl2ZUxpbmVNYXJrZXJfMSIsImV2ZW50c18xIiwibWVzc2FnaW5nXzEiLCJ0aHJvdHRsZSIsInNjcm9sbERpc2FibGVkIiwibWFya2VyIiwic2V0dGluZ3MiLCJhY3F1aXJlVnNDb2RlQXBpIiwic3RhdGUiLCJzZXRTdGF0ZSIsIm1lc3NhZ2luZyIsImNzcEFsZXJ0ZXIiLCJzZXRQb3N0ZXIiLCJzdHlsZUxvYWRpbmdNb25pdG9yIiwib25sb2FkIiwidXBkYXRlSW1hZ2VTaXplcyIsImluaXRpYWxMaW5lIiwib25VcGRhdGVWaWV3IiwiZG9TY3JvbGwiLCJpbWFnZUluZm8iLCJpbWFnZXMiLCJnZXRFbGVtZW50c0J5VGFnTmFtZSIsImltZyIsImNsYXNzTGlzdCIsImNvbnRhaW5zIiwicmVtb3ZlIiwid2lkdGgiLCJldmVudCIsIm9uRGlkQ2hhbmdlVGV4dEVkaXRvclNlbGVjdGlvbiIsImRvdWJsZUNsaWNrVG9Td2l0Y2hUb0VkaXRvciIsIm5vZGUiLCJ0YXJnZXQiLCJwYXJlbnROb2RlIiwidGFnTmFtZSIsInBhZ2VZIiwicGFzc1Rocm91Z2hMaW5rU2NoZW1lcyIsImhyZWYiLCJzdGFydHNXaXRoIiwic29tZSIsInNjaGVtZSIsImhyZWZUZXh0IiwicHJldmVudERlZmF1bHQiLCJzdG9wUHJvcGFnYXRpb24iXSwibWFwcGluZ3MiOiJhQUNBLElBQUFBLEtBR0EsU0FBQUMsRUFBQUMsR0FHQSxHQUFBRixFQUFBRSxHQUNBLE9BQUFGLEVBQUFFLEdBQUFDLFFBR0EsSUFBQUMsRUFBQUosRUFBQUUsSUFDQUcsRUFBQUgsRUFDQUksR0FBQSxFQUNBSCxZQVVBLE9BTkFJLEVBQUFMLEdBQUFNLEtBQUFKLEVBQUFELFFBQUFDLElBQUFELFFBQUFGLEdBR0FHLEVBQUFFLEdBQUEsRUFHQUYsRUFBQUQsUUFLQUYsRUFBQVEsRUFBQUYsRUFHQU4sRUFBQVMsRUFBQVYsRUFHQUMsRUFBQVUsRUFBQSxTQUFBUixFQUFBUyxFQUFBQyxHQUNBWixFQUFBYSxFQUFBWCxFQUFBUyxJQUNBRyxPQUFBQyxlQUFBYixFQUFBUyxHQUNBSyxjQUFBLEVBQ0FDLFlBQUEsRUFDQUMsSUFBQU4sS0FNQVosRUFBQW1CLEVBQUEsU0FBQWpCLEdBQ0FZLE9BQUFDLGVBQUFiLEVBQUEsY0FBaURrQixPQUFBLEtBSWpEcEIsRUFBQXFCLEVBQUEsU0FBQWxCLEdBQ0EsSUFBQVMsRUFBQVQsS0FBQW1CLFdBQ0EsV0FBMkIsT0FBQW5CLEVBQUEsU0FDM0IsV0FB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LElBQ0EsSUFBQUEsRUFDQSxPQUVBLElBQUFHLEVBQUFILEVBQUFJLE9BQ0EsS0FBQUQsR0FBQSxDQUNBLEdBQUFBLEVBQUFHLFNBQUEsTUFBQUgsRUFBQUcsU0FBQUgsRUFBQU0sS0FBQSxDQUNBLEdBQUFOLEVBQUE5SyxhQUFBLFFBQUFxTCxXQUFBLEtBQ0EsT0FHQSxHQUFBRixFQUFBRyxLQUFBQyxHQUFBVCxFQUFBTSxLQUFBQyxXQUFBRSxJQUNBLE9BRUEsTUFBQUMsRUFBQVYsRUFBQTlLLGFBQUEsY0FBQThLLEVBQUE5SyxhQUFBLFFBRUEsb0JBQUE2SCxLQUFBMkQsUUFNQSxHQUxBOUIsRUFBQXhCLFlBQUEsWUFBbURrRCxLQUFBSSxJQUNuRGIsRUFBQWMsc0JBQ0FkLEVBQUFlLG1CQUtBWixJQUFBRSxjQUVDLEdBQ0RuSixPQUFBMEcsaUJBQUEsU0FBQVksRUFBQSxLQUNBLEdBQUFDLEVBQ0FBLEdBQUEsTUFFQSxDQUNBLE1BQUE3SSxFQUFBaUksRUFBQXZGLGlDQUFBcEIsT0FBQUMsU0FDQSxpQkFBQXZCLEdBQUFTLE1BQUFULEtBQ0FtSixFQUFBeEIsWUFBQSxjQUFpRDNILFNBQ2pEaUosRUFBQWpKLE9BQ0F5SCxFQUFBeUIsU0FBQUQsTUFHQyIsImZpbGUiOiJpbmRleC5qcyIsInNvdXJjZXNDb250ZW50IjpbIiBcdC8vIFRoZSBtb2R1bGUgY2FjaGVcbiBcdHZhciBpbnN0YWxsZWRNb2R1bGVzID0ge307XG5cbiBcdC8vIFRoZSByZXF1aXJlIGZ1bmN0aW9uXG4gXHRmdW5jdGlvbiBfX3dlYnBhY2tfcmVxdWlyZV9fKG1vZHVsZUlkKSB7XG5cbiBcdFx0Ly8gQ2hlY2sgaWYgbW9kdWxlIGlzIGluIGNhY2hlXG4gXHRcdGlmKGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdKSB7XG4gXHRcdFx0cmV0dXJuIGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdLmV4cG9ydHM7XG4gXHRcdH1cbiBcdFx0Ly8gQ3JlYXRlIGEgbmV3IG1vZHVsZSAoYW5kIHB1dCBpdCBpbnRvIHRoZSBjYWNoZSlcbiBcdFx0dmFyIG1vZHVsZSA9IGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdID0ge1xuIFx0XHRcdGk6IG1vZHVsZUlkLFxuIFx0XHRcdGw6IGZhbHNlLFxuIFx0XHRcdGV4cG9ydHM6IHt9XG4gXHRcdH07XG5cbiBcdFx0Ly8gRXhlY3V0ZSB0aGUgbW9kdWxlIGZ1bmN0aW9uXG4gXHRcdG1vZHVsZXNbbW9kdWxlSWRdLmNhbGwobW9kdWxlLmV4cG9ydHMsIG1vZHVsZSwgbW9kdWxlLmV4cG9ydHMsIF9fd2VicGFja19yZXF1aXJlX18pO1xuXG4gXHRcdC8vIEZsYWcgdGhlIG1vZHVsZSBhcyBsb2FkZWRcbiBcdFx0bW9kdWxlLmwgPSB0cnVlO1xuXG4gXHRcdC8vIFJldHVybiB0aGUgZXhwb3J0cyBvZiB0aGUgbW9kdWxlXG4gXHRcdHJldHVybiBtb2R1bGUuZXhwb3J0cztcbiBcdH1cblxuXG4gXHQvLyBleHBvc2UgdGhlIG1vZHVsZXMgb2JqZWN0IChfX3dlYnBhY2tfbW9kdWxlc19fKVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5tID0gbW9kdWxlcztcblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGUgY2FjaGVcbiBcdF9fd2VicGFja19yZXF1aXJlX18uYyA9IGluc3RhbGxlZE1vZHVsZXM7XG5cbiBcdC8vIGRlZmluZSBnZXR0ZXIgZnVuY3Rpb24gZm9yIGhhcm1vbnkgZXhwb3J0c1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5kID0gZnVuY3Rpb24oZXhwb3J0cywgbmFtZSwgZ2V0dGVyKSB7XG4gXHRcdGlmKCFfX3dlYnBhY2tfcmVxdWlyZV9fLm8oZXhwb3J0cywgbmFtZSkpIHtcbiBcdFx0XHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgbmFtZSwge1xuIFx0XHRcdFx0Y29uZmlndXJhYmxlOiBmYWxzZSxcbiBcdFx0XHRcdGVudW1lcmFibGU6IHRydWUsXG4gXHRcdFx0XHRnZXQ6IGdldHRlclxuIFx0XHRcdH0pO1xuIFx0XHR9XG4gXHR9O1xuXG4gXHQvLyBkZWZpbmUgX19lc01vZHVsZSBvbiBleHBvcnRzXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnIgPSBmdW5jdGlvbihleHBvcnRzKSB7XG4gXHRcdE9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCAnX19lc01vZHVsZScsIHsgdmFsdWU6IHRydWUgfSk7XG4gXHR9O1xuXG4gXHQvLyBnZXREZWZhdWx0RXhwb3J0IGZ1bmN0aW9uIGZvciBjb21wYXRpYmlsaXR5IHdpdGggbm9uLWhhcm1vbnkgbW9kdWxlc1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5uID0gZnVuY3Rpb24obW9kdWxlKSB7XG4gXHRcdHZhciBnZXR0ZXIgPSBtb2R1bGUgJiYgbW9kdWxlLl9fZXNNb2R1bGUgP1xuIFx0XHRcdGZ1bmN0aW9uIGdldERlZmF1bHQoKSB7IHJldHVybiBtb2R1bGVbJ2RlZmF1bHQnXTsgfSA6XG4gXHRcdFx0ZnVuY3Rpb24gZ2V0TW9kdWxlRXhwb3J0cygpIHsgcmV0dXJuIG1vZHVsZTsgfTtcbiBcdFx0X193ZWJwYWNrX3JlcXVpcmVfXy5kKGdldHRlciwgJ2EnLCBnZXR0ZXIpO1xuIFx0XHRyZXR1cm4gZ2V0dGVyO1xuIFx0fTtcblxuIFx0Ly8gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm8gPSBmdW5jdGlvbihvYmplY3QsIHByb3BlcnR5KSB7IHJldHVybiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwob2JqZWN0LCBwcm9wZXJ0eSk7IH07XG5cbiBcdC8vIF9fd2VicGFja19wdWJsaWNfcGF0aF9fXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnAgPSBcIlwiO1xuXG5cbiBcdC8vIExvYWQgZW50cnkgbW9kdWxlIGFuZCByZXR1cm4gZXhwb3J0c1xuIFx0cmV0dXJuIF9fd2VicGFja19yZXF1aXJlX18oX193ZWJwYWNrX3JlcXVpcmVfXy5zID0gMTEpO1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwgeyB2YWx1ZTogdHJ1ZSB9KTtcbmxldCBjYWNoZWRTZXR0aW5ncyA9IHVuZGVmaW5lZDtcbmZ1bmN0aW9uIGdldERhdGEoa2V5KSB7XG4gICAgY29uc3QgZWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd2c2NvZGUtbWFya2Rvd24tcHJldmlldy1kYXRhJyk7XG4gICAgaWYgKGVsZW1lbnQpIHtcbiAgICAgICAgY29uc3QgZGF0YSA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKGtleSk7XG4gICAgICAgIGlmIChkYXRhKSB7XG4gICAgICAgICAgICByZXR1cm4gSlNPTi5wYXJzZShkYXRhKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICB0aHJvdyBuZXcgRXJyb3IoYENvdWxkIG5vdCBsb2FkIGRhdGEgZm9yICR7a2V5fWApO1xufVxuZXhwb3J0cy5nZXREYXRhID0gZ2V0RGF0YTtcbmZ1bmN0aW9uIGdldFNldHRpbmdzKCkge1xuICAgIGlmIChjYWNoZWRTZXR0aW5ncykge1xuICAgICAgICByZXR1cm4gY2FjaGVkU2V0dGluZ3M7XG4gICAgfVxuICAgIGNhY2hlZFNldHRpbmdzID0gZ2V0RGF0YSgnZGF0YS1zZXR0aW5ncycpO1xuICAgIGlmIChjYWNoZWRTZXR0aW5ncykge1xuICAgICAgICByZXR1cm4gY2FjaGVkU2V0dGluZ3M7XG4gICAgfVxuICAgIHRocm93IG5ldyBFcnJvcignQ291bGQgbm90IGxvYWQgc2V0dGluZ3MnKTtcbn1cbmV4cG9ydHMuZ2V0U2V0dGluZ3MgPSBnZXRTZXR0aW5ncztcbiIsIlwidXNlIHN0cmljdFwiO1xuLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgXCJfX2VzTW9kdWxlXCIsIHsgdmFsdWU6IHRydWUgfSk7XG5jb25zdCBzZXR0aW5nc18xID0gcmVxdWlyZShcIi4vc2V0dGluZ3NcIik7XG5mdW5jdGlvbiBjbGFtcChtaW4sIG1heCwgdmFsdWUpIHtcbiAgICByZXR1cm4gTWF0aC5taW4obWF4LCBNYXRoLm1heChtaW4sIHZhbHVlKSk7XG59XG5mdW5jdGlvbiBjbGFtcExpbmUobGluZSkge1xuICAgIHJldHVybiBjbGFtcCgwLCBzZXR0aW5nc18xLmdldFNldHRpbmdzKCkubGluZUNvdW50IC0gMSwgbGluZSk7XG59XG5jb25zdCBnZXRDb2RlTGluZUVsZW1lbnRzID0gKCgpID0+IHtcbiAgICBsZXQgZWxlbWVudHM7XG4gICAgcmV0dXJuICgpID0+IHtcbiAgICAgICAgaWYgKCFlbGVtZW50cykge1xuICAgICAgICAgICAgZWxlbWVudHMgPSBbeyBlbGVtZW50OiBkb2N1bWVudC5ib2R5LCBsaW5lOiAwIH1dO1xuICAgICAgICAgICAgZm9yIChjb25zdCBlbGVtZW50IG9mIGRvY3VtZW50LmdldEVsZW1lbnRzQnlDbGFzc05hbWUoJ2NvZGUtbGluZScpKSB7XG4gICAgICAgICAgICAgICAgY29uc3QgbGluZSA9ICtlbGVtZW50LmdldEF0dHJpYnV0ZSgnZGF0YS1saW5lJyk7XG4gICAgICAgICAgICAgICAgaWYgKCFpc05hTihsaW5lKSkge1xuICAgICAgICAgICAgICAgICAgICBlbGVtZW50cy5wdXNoKHsgZWxlbWVudDogZWxlbWVudCwgbGluZSB9KTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGVsZW1lbnRzO1xuICAgIH07XG59KSgpO1xuLyoqXG4gKiBGaW5kIHRoZSBodG1sIGVsZW1lbnRzIHRoYXQgbWFwIHRvIGEgc3BlY2lmaWMgdGFyZ2V0IGxpbmUgaW4gdGhlIGVkaXRvci5cbiAqXG4gKiBJZiBhbiBleGFjdCBtYXRjaCwgcmV0dXJucyBhIHNpbmdsZSBlbGVtZW50LiBJZiB0aGUgbGluZSBpcyBiZXR3ZWVuIGVsZW1lbnRzLFxuICogcmV0dXJucyB0aGUgZWxlbWVudCBwcmlvciB0byBhbmQgdGhlIGVsZW1lbnQgYWZ0ZXIgdGhlIGdpdmVuIGxpbmUuXG4gKi9cbmZ1bmN0aW9uIGdldEVsZW1lbnRzRm9yU291cmNlTGluZSh0YXJnZXRMaW5lKSB7XG4gICAgY29uc3QgbGluZU51bWJlciA9IE1hdGguZmxvb3IodGFyZ2V0TGluZSk7XG4gICAgY29uc3QgbGluZXMgPSBnZXRDb2RlTGluZUVsZW1lbnRzKCk7XG4gICAgbGV0IHByZXZpb3VzID0gbGluZXNbMF0gfHwgbnVsbDtcbiAgICBmb3IgKGNvbnN0IGVudHJ5IG9mIGxpbmVzKSB7XG4gICAgICAgIGlmIChlbnRyeS5saW5lID09PSBsaW5lTnVtYmVyKSB7XG4gICAgICAgICAgICByZXR1cm4geyBwcmV2aW91czogZW50cnksIG5leHQ6IHVuZGVmaW5lZCB9O1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKGVudHJ5LmxpbmUgPiBsaW5lTnVtYmVyKSB7XG4gICAgICAgICAgICByZXR1cm4geyBwcmV2aW91cywgbmV4dDogZW50cnkgfTtcbiAgICAgICAgfVxuICAgICAgICBwcmV2aW91cyA9IGVudHJ5O1xuICAgIH1cbiAgICByZXR1cm4geyBwcmV2aW91cyB9O1xufVxuZXhwb3J0cy5nZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUgPSBnZXRFbGVtZW50c0ZvclNvdXJjZUxpbmU7XG4vKipcbiAqIEZpbmQgdGhlIGh0bWwgZWxlbWVudHMgdGhhdCBhcmUgYXQgYSBzcGVjaWZpYyBwaXhlbCBvZmZzZXQgb24gdGhlIHBhZ2UuXG4gKi9cbmZ1bmN0aW9uIGdldExpbmVFbGVtZW50c0F0UGFnZU9mZnNldChvZmZzZXQpIHtcbiAgICBjb25zdCBsaW5lcyA9IGdldENvZGVMaW5lRWxlbWVudHMoKTtcbiAgICBjb25zdCBwb3NpdGlvbiA9IG9mZnNldCAtIHdpbmRvdy5zY3JvbGxZO1xuICAgIGxldCBsbyA9IC0xO1xuICAgIGxldCBoaSA9IGxpbmVzLmxlbmd0aCAtIDE7XG4gICAgd2hpbGUgKGxvICsgMSA8IGhpKSB7XG4gICAgICAgIGNvbnN0IG1pZCA9IE1hdGguZmxvb3IoKGxvICsgaGkpIC8gMik7XG4gICAgICAgIGNvbnN0IGJvdW5kcyA9IGxpbmVzW21pZF0uZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcbiAgICAgICAgaWYgKGJvdW5kcy50b3AgKyBib3VuZHMuaGVpZ2h0ID49IHBvc2l0aW9uKSB7XG4gICAgICAgICAgICBoaSA9IG1pZDtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGxvID0gbWlkO1xuICAgICAgICB9XG4gICAgfVxuICAgIGNvbnN0IGhpRWxlbWVudCA9IGxpbmVzW2hpXTtcbiAgICBjb25zdCBoaUJvdW5kcyA9IGhpRWxlbWVudC5lbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICAgIGlmIChoaSA+PSAxICYmIGhpQm91bmRzLnRvcCA+IHBvc2l0aW9uKSB7XG4gICAgICAgIGNvbnN0IGxvRWxlbWVudCA9IGxpbmVzW2xvXTtcbiAgICAgICAgcmV0dXJuIHsgcHJldmlvdXM6IGxvRWxlbWVudCwgbmV4dDogaGlFbGVtZW50IH07XG4gICAgfVxuICAgIHJldHVybiB7IHByZXZpb3VzOiBoaUVsZW1lbnQgfTtcbn1cbmV4cG9ydHMuZ2V0TGluZUVsZW1lbnRzQXRQYWdlT2Zmc2V0ID0gZ2V0TGluZUVsZW1lbnRzQXRQYWdlT2Zmc2V0O1xuLyoqXG4gKiBBdHRlbXB0IHRvIHJldmVhbCB0aGUgZWxlbWVudCBmb3IgYSBzb3VyY2UgbGluZSBpbiB0aGUgZWRpdG9yLlxuICovXG5mdW5jdGlvbiBzY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUobGluZSkge1xuICAgIGlmICghc2V0dGluZ3NfMS5nZXRTZXR0aW5ncygpLnNjcm9sbFByZXZpZXdXaXRoRWRpdG9yKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKGxpbmUgPD0gMCkge1xuICAgICAgICB3aW5kb3cuc2Nyb2xsKHdpbmRvdy5zY3JvbGxYLCAwKTtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCB7IHByZXZpb3VzLCBuZXh0IH0gPSBnZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUobGluZSk7XG4gICAgaWYgKCFwcmV2aW91cykge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGxldCBzY3JvbGxUbyA9IDA7XG4gICAgY29uc3QgcmVjdCA9IHByZXZpb3VzLmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG4gICAgY29uc3QgcHJldmlvdXNUb3AgPSByZWN0LnRvcDtcbiAgICBpZiAobmV4dCAmJiBuZXh0LmxpbmUgIT09IHByZXZpb3VzLmxpbmUpIHtcbiAgICAgICAgLy8gQmV0d2VlbiB0d28gZWxlbWVudHMuIEdvIHRvIHBlcmNlbnRhZ2Ugb2Zmc2V0IGJldHdlZW4gdGhlbS5cbiAgICAgICAgY29uc3QgYmV0d2VlblByb2dyZXNzID0gKGxpbmUgLSBwcmV2aW91cy5saW5lKSAvIChuZXh0LmxpbmUgLSBwcmV2aW91cy5saW5lKTtcbiAgICAgICAgY29uc3QgZWxlbWVudE9mZnNldCA9IG5leHQuZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS50b3AgLSBwcmV2aW91c1RvcDtcbiAgICAgICAgc2Nyb2xsVG8gPSBwcmV2aW91c1RvcCArIGJldHdlZW5Qcm9ncmVzcyAqIGVsZW1lbnRPZmZzZXQ7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBjb25zdCBwcm9ncmVzc0luRWxlbWVudCA9IGxpbmUgLSBNYXRoLmZsb29yKGxpbmUpO1xuICAgICAgICBzY3JvbGxUbyA9IHByZXZpb3VzVG9wICsgKHJlY3QuaGVpZ2h0ICogcHJvZ3Jlc3NJbkVsZW1lbnQpO1xuICAgIH1cbiAgICB3aW5kb3cuc2Nyb2xsKHdpbmRvdy5zY3JvbGxYLCBNYXRoLm1heCgxLCB3aW5kb3cuc2Nyb2xsWSArIHNjcm9sbFRvKSk7XG59XG5leHBvcnRzLnNjcm9sbFRvUmV2ZWFsU291cmNlTGluZSA9IHNjcm9sbFRvUmV2ZWFsU291cmNlTGluZTtcbmZ1bmN0aW9uIGdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0KG9mZnNldCkge1xuICAgIGNvbnN0IHsgcHJldmlvdXMsIG5leHQgfSA9IGdldExpbmVFbGVtZW50c0F0UGFnZU9mZnNldChvZmZzZXQpO1xuICAgIGlmIChwcmV2aW91cykge1xuICAgICAgICBjb25zdCBwcmV2aW91c0JvdW5kcyA9IHByZXZpb3VzLmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG4gICAgICAgIGNvbnN0IG9mZnNldEZyb21QcmV2aW91cyA9IChvZmZzZXQgLSB3aW5kb3cuc2Nyb2xsWSAtIHByZXZpb3VzQm91bmRzLnRvcCk7XG4gICAgICAgIGlmIChuZXh0KSB7XG4gICAgICAgICAgICBjb25zdCBwcm9ncmVzc0JldHdlZW5FbGVtZW50cyA9IG9mZnNldEZyb21QcmV2aW91cyAvIChuZXh0LmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkudG9wIC0gcHJldmlvdXNCb3VuZHMudG9wKTtcbiAgICAgICAgICAgIGNvbnN0IGxpbmUgPSBwcmV2aW91cy5saW5lICsgcHJvZ3Jlc3NCZXR3ZWVuRWxlbWVudHMgKiAobmV4dC5saW5lIC0gcHJldmlvdXMubGluZSk7XG4gICAgICAgICAgICByZXR1cm4gY2xhbXBMaW5lKGxpbmUpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgY29uc3QgcHJvZ3Jlc3NXaXRoaW5FbGVtZW50ID0gb2Zmc2V0RnJvbVByZXZpb3VzIC8gKHByZXZpb3VzQm91bmRzLmhlaWdodCk7XG4gICAgICAgICAgICBjb25zdCBsaW5lID0gcHJldmlvdXMubGluZSArIHByb2dyZXNzV2l0aGluRWxlbWVudDtcbiAgICAgICAgICAgIHJldHVybiBjbGFtcExpbmUobGluZSk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIG51bGw7XG59XG5leHBvcnRzLmdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0ID0gZ2V0RWRpdG9yTGluZU51bWJlckZvclBhZ2VPZmZzZXQ7XG4vKipcbiAqIFRyeSB0byBmaW5kIHRoZSBodG1sIGVsZW1lbnQgYnkgdXNpbmcgYSBmcmFnbWVudCBpZFxuICovXG5mdW5jdGlvbiBnZXRMaW5lRWxlbWVudEZvckZyYWdtZW50KGZyYWdtZW50KSB7XG4gICAgcmV0dXJuIGdldENvZGVMaW5lRWxlbWVudHMoKS5maW5kKChlbGVtZW50KSA9PiB7XG4gICAgICAgIHJldHVybiBlbGVtZW50LmVsZW1lbnQuaWQgPT09IGZyYWdtZW50O1xuICAgIH0pO1xufVxuZXhwb3J0cy5nZXRMaW5lRWxlbWVudEZvckZyYWdtZW50ID0gZ2V0TGluZUVsZW1lbnRGb3JGcmFnbWVudDtcbiIsInZhciBnO1xyXG5cclxuLy8gVGhpcyB3b3JrcyBpbiBub24tc3RyaWN0IG1vZGVcclxuZyA9IChmdW5jdGlvbigpIHtcclxuXHRyZXR1cm4gdGhpcztcclxufSkoKTtcclxuXHJcbnRyeSB7XHJcblx0Ly8gVGhpcyB3b3JrcyBpZiBldmFsIGlzIGFsbG93ZWQgKHNlZSBDU1ApXHJcblx0ZyA9IGcgfHwgRnVuY3Rpb24oXCJyZXR1cm4gdGhpc1wiKSgpIHx8ICgxLCBldmFsKShcInRoaXNcIik7XHJcbn0gY2F0Y2ggKGUpIHtcclxuXHQvLyBUaGlzIHdvcmtzIGlmIHRoZSB3aW5kb3cgcmVmZXJlbmNlIGlzIGF2YWlsYWJsZVxyXG5cdGlmICh0eXBlb2Ygd2luZG93ID09PSBcIm9iamVjdFwiKSBnID0gd2luZG93O1xyXG59XHJcblxyXG4vLyBnIGNhbiBzdGlsbCBiZSB1bmRlZmluZWQsIGJ1dCBub3RoaW5nIHRvIGRvIGFib3V0IGl0Li4uXHJcbi8vIFdlIHJldHVybiB1bmRlZmluZWQsIGluc3RlYWQgb2Ygbm90aGluZyBoZXJlLCBzbyBpdCdzXHJcbi8vIGVhc2llciB0byBoYW5kbGUgdGhpcyBjYXNlLiBpZighZ2xvYmFsKSB7IC4uLn1cclxuXHJcbm1vZHVsZS5leHBvcnRzID0gZztcclxuIiwiLyoqXG4gKiBsb2Rhc2ggKEN1c3RvbSBCdWlsZCkgPGh0dHBzOi8vbG9kYXNoLmNvbS8+XG4gKiBCdWlsZDogYGxvZGFzaCBtb2R1bGFyaXplIGV4cG9ydHM9XCJucG1cIiAtbyAuL2BcbiAqIENvcHlyaWdodCBqUXVlcnkgRm91bmRhdGlvbiBhbmQgb3RoZXIgY29udHJpYnV0b3JzIDxodHRwczovL2pxdWVyeS5vcmcvPlxuICogUmVsZWFzZWQgdW5kZXIgTUlUIGxpY2Vuc2UgPGh0dHBzOi8vbG9kYXNoLmNvbS9saWNlbnNlPlxuICogQmFzZWQgb24gVW5kZXJzY29yZS5qcyAxLjguMyA8aHR0cDovL3VuZGVyc2NvcmVqcy5vcmcvTElDRU5TRT5cbiAqIENvcHlyaWdodCBKZXJlbXkgQXNoa2VuYXMsIERvY3VtZW50Q2xvdWQgYW5kIEludmVzdGlnYXRpdmUgUmVwb3J0ZXJzICYgRWRpdG9yc1xuICovXG5cbi8qKiBVc2VkIGFzIHRoZSBgVHlwZUVycm9yYCBtZXNzYWdlIGZvciBcIkZ1bmN0aW9uc1wiIG1ldGhvZHMuICovXG52YXIgRlVOQ19FUlJPUl9URVhUID0gJ0V4cGVjdGVkIGEgZnVuY3Rpb24nO1xuXG4vKiogVXNlZCBhcyByZWZlcmVuY2VzIGZvciB2YXJpb3VzIGBOdW1iZXJgIGNvbnN0YW50cy4gKi9cbnZhciBOQU4gPSAwIC8gMDtcblxuLyoqIGBPYmplY3QjdG9TdHJpbmdgIHJlc3VsdCByZWZlcmVuY2VzLiAqL1xudmFyIHN5bWJvbFRhZyA9ICdbb2JqZWN0IFN5bWJvbF0nO1xuXG4vKiogVXNlZCB0byBtYXRjaCBsZWFkaW5nIGFuZCB0cmFpbGluZyB3aGl0ZXNwYWNlLiAqL1xudmFyIHJlVHJpbSA9IC9eXFxzK3xcXHMrJC9nO1xuXG4vKiogVXNlZCB0byBkZXRlY3QgYmFkIHNpZ25lZCBoZXhhZGVjaW1hbCBzdHJpbmcgdmFsdWVzLiAqL1xudmFyIHJlSXNCYWRIZXggPSAvXlstK10weFswLTlhLWZdKyQvaTtcblxuLyoqIFVzZWQgdG8gZGV0ZWN0IGJpbmFyeSBzdHJpbmcgdmFsdWVzLiAqL1xudmFyIHJlSXNCaW5hcnkgPSAvXjBiWzAxXSskL2k7XG5cbi8qKiBVc2VkIHRvIGRldGVjdCBvY3RhbCBzdHJpbmcgdmFsdWVzLiAqL1xudmFyIHJlSXNPY3RhbCA9IC9eMG9bMC03XSskL2k7XG5cbi8qKiBCdWlsdC1pbiBtZXRob2QgcmVmZXJlbmNlcyB3aXRob3V0IGEgZGVwZW5kZW5jeSBvbiBgcm9vdGAuICovXG52YXIgZnJlZVBhcnNlSW50ID0gcGFyc2VJbnQ7XG5cbi8qKiBEZXRlY3QgZnJlZSB2YXJpYWJsZSBgZ2xvYmFsYCBmcm9tIE5vZGUuanMuICovXG52YXIgZnJlZUdsb2JhbCA9IHR5cGVvZiBnbG9iYWwgPT0gJ29iamVjdCcgJiYgZ2xvYmFsICYmIGdsb2JhbC5PYmplY3QgPT09IE9iamVjdCAmJiBnbG9iYWw7XG5cbi8qKiBEZXRlY3QgZnJlZSB2YXJpYWJsZSBgc2VsZmAuICovXG52YXIgZnJlZVNlbGYgPSB0eXBlb2Ygc2VsZiA9PSAnb2JqZWN0JyAmJiBzZWxmICYmIHNlbGYuT2JqZWN0ID09PSBPYmplY3QgJiYgc2VsZjtcblxuLyoqIFVzZWQgYXMgYSByZWZlcmVuY2UgdG8gdGhlIGdsb2JhbCBvYmplY3QuICovXG52YXIgcm9vdCA9IGZyZWVHbG9iYWwgfHwgZnJlZVNlbGYgfHwgRnVuY3Rpb24oJ3JldHVybiB0aGlzJykoKTtcblxuLyoqIFVzZWQgZm9yIGJ1aWx0LWluIG1ldGhvZCByZWZlcmVuY2VzLiAqL1xudmFyIG9iamVjdFByb3RvID0gT2JqZWN0LnByb3RvdHlwZTtcblxuLyoqXG4gKiBVc2VkIHRvIHJlc29sdmUgdGhlXG4gKiBbYHRvU3RyaW5nVGFnYF0oaHR0cDovL2VjbWEtaW50ZXJuYXRpb25hbC5vcmcvZWNtYS0yNjIvNy4wLyNzZWMtb2JqZWN0LnByb3RvdHlwZS50b3N0cmluZylcbiAqIG9mIHZhbHVlcy5cbiAqL1xudmFyIG9iamVjdFRvU3RyaW5nID0gb2JqZWN0UHJvdG8udG9TdHJpbmc7XG5cbi8qIEJ1aWx0LWluIG1ldGhvZCByZWZlcmVuY2VzIGZvciB0aG9zZSB3aXRoIHRoZSBzYW1lIG5hbWUgYXMgb3RoZXIgYGxvZGFzaGAgbWV0aG9kcy4gKi9cbnZhciBuYXRpdmVNYXggPSBNYXRoLm1heCxcbiAgICBuYXRpdmVNaW4gPSBNYXRoLm1pbjtcblxuLyoqXG4gKiBHZXRzIHRoZSB0aW1lc3RhbXAgb2YgdGhlIG51bWJlciBvZiBtaWxsaXNlY29uZHMgdGhhdCBoYXZlIGVsYXBzZWQgc2luY2VcbiAqIHRoZSBVbml4IGVwb2NoICgxIEphbnVhcnkgMTk3MCAwMDowMDowMCBVVEMpLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgMi40LjBcbiAqIEBjYXRlZ29yeSBEYXRlXG4gKiBAcmV0dXJucyB7bnVtYmVyfSBSZXR1cm5zIHRoZSB0aW1lc3RhbXAuXG4gKiBAZXhhbXBsZVxuICpcbiAqIF8uZGVmZXIoZnVuY3Rpb24oc3RhbXApIHtcbiAqICAgY29uc29sZS5sb2coXy5ub3coKSAtIHN0YW1wKTtcbiAqIH0sIF8ubm93KCkpO1xuICogLy8gPT4gTG9ncyB0aGUgbnVtYmVyIG9mIG1pbGxpc2Vjb25kcyBpdCB0b29rIGZvciB0aGUgZGVmZXJyZWQgaW52b2NhdGlvbi5cbiAqL1xudmFyIG5vdyA9IGZ1bmN0aW9uKCkge1xuICByZXR1cm4gcm9vdC5EYXRlLm5vdygpO1xufTtcblxuLyoqXG4gKiBDcmVhdGVzIGEgZGVib3VuY2VkIGZ1bmN0aW9uIHRoYXQgZGVsYXlzIGludm9raW5nIGBmdW5jYCB1bnRpbCBhZnRlciBgd2FpdGBcbiAqIG1pbGxpc2Vjb25kcyBoYXZlIGVsYXBzZWQgc2luY2UgdGhlIGxhc3QgdGltZSB0aGUgZGVib3VuY2VkIGZ1bmN0aW9uIHdhc1xuICogaW52b2tlZC4gVGhlIGRlYm91bmNlZCBmdW5jdGlvbiBjb21lcyB3aXRoIGEgYGNhbmNlbGAgbWV0aG9kIHRvIGNhbmNlbFxuICogZGVsYXllZCBgZnVuY2AgaW52b2NhdGlvbnMgYW5kIGEgYGZsdXNoYCBtZXRob2QgdG8gaW1tZWRpYXRlbHkgaW52b2tlIHRoZW0uXG4gKiBQcm92aWRlIGBvcHRpb25zYCB0byBpbmRpY2F0ZSB3aGV0aGVyIGBmdW5jYCBzaG91bGQgYmUgaW52b2tlZCBvbiB0aGVcbiAqIGxlYWRpbmcgYW5kL29yIHRyYWlsaW5nIGVkZ2Ugb2YgdGhlIGB3YWl0YCB0aW1lb3V0LiBUaGUgYGZ1bmNgIGlzIGludm9rZWRcbiAqIHdpdGggdGhlIGxhc3QgYXJndW1lbnRzIHByb3ZpZGVkIHRvIHRoZSBkZWJvdW5jZWQgZnVuY3Rpb24uIFN1YnNlcXVlbnRcbiAqIGNhbGxzIHRvIHRoZSBkZWJvdW5jZWQgZnVuY3Rpb24gcmV0dXJuIHRoZSByZXN1bHQgb2YgdGhlIGxhc3QgYGZ1bmNgXG4gKiBpbnZvY2F0aW9uLlxuICpcbiAqICoqTm90ZToqKiBJZiBgbGVhZGluZ2AgYW5kIGB0cmFpbGluZ2Agb3B0aW9ucyBhcmUgYHRydWVgLCBgZnVuY2AgaXNcbiAqIGludm9rZWQgb24gdGhlIHRyYWlsaW5nIGVkZ2Ugb2YgdGhlIHRpbWVvdXQgb25seSBpZiB0aGUgZGVib3VuY2VkIGZ1bmN0aW9uXG4gKiBpcyBpbnZva2VkIG1vcmUgdGhhbiBvbmNlIGR1cmluZyB0aGUgYHdhaXRgIHRpbWVvdXQuXG4gKlxuICogSWYgYHdhaXRgIGlzIGAwYCBhbmQgYGxlYWRpbmdgIGlzIGBmYWxzZWAsIGBmdW5jYCBpbnZvY2F0aW9uIGlzIGRlZmVycmVkXG4gKiB1bnRpbCB0byB0aGUgbmV4dCB0aWNrLCBzaW1pbGFyIHRvIGBzZXRUaW1lb3V0YCB3aXRoIGEgdGltZW91dCBvZiBgMGAuXG4gKlxuICogU2VlIFtEYXZpZCBDb3JiYWNobydzIGFydGljbGVdKGh0dHBzOi8vY3NzLXRyaWNrcy5jb20vZGVib3VuY2luZy10aHJvdHRsaW5nLWV4cGxhaW5lZC1leGFtcGxlcy8pXG4gKiBmb3IgZGV0YWlscyBvdmVyIHRoZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGBfLmRlYm91bmNlYCBhbmQgYF8udGhyb3R0bGVgLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgMC4xLjBcbiAqIEBjYXRlZ29yeSBGdW5jdGlvblxuICogQHBhcmFtIHtGdW5jdGlvbn0gZnVuYyBUaGUgZnVuY3Rpb24gdG8gZGVib3VuY2UuXG4gKiBAcGFyYW0ge251bWJlcn0gW3dhaXQ9MF0gVGhlIG51bWJlciBvZiBtaWxsaXNlY29uZHMgdG8gZGVsYXkuXG4gKiBAcGFyYW0ge09iamVjdH0gW29wdGlvbnM9e31dIFRoZSBvcHRpb25zIG9iamVjdC5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW29wdGlvbnMubGVhZGluZz1mYWxzZV1cbiAqICBTcGVjaWZ5IGludm9raW5nIG9uIHRoZSBsZWFkaW5nIGVkZ2Ugb2YgdGhlIHRpbWVvdXQuXG4gKiBAcGFyYW0ge251bWJlcn0gW29wdGlvbnMubWF4V2FpdF1cbiAqICBUaGUgbWF4aW11bSB0aW1lIGBmdW5jYCBpcyBhbGxvd2VkIHRvIGJlIGRlbGF5ZWQgYmVmb3JlIGl0J3MgaW52b2tlZC5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW29wdGlvbnMudHJhaWxpbmc9dHJ1ZV1cbiAqICBTcGVjaWZ5IGludm9raW5nIG9uIHRoZSB0cmFpbGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0LlxuICogQHJldHVybnMge0Z1bmN0aW9ufSBSZXR1cm5zIHRoZSBuZXcgZGVib3VuY2VkIGZ1bmN0aW9uLlxuICogQGV4YW1wbGVcbiAqXG4gKiAvLyBBdm9pZCBjb3N0bHkgY2FsY3VsYXRpb25zIHdoaWxlIHRoZSB3aW5kb3cgc2l6ZSBpcyBpbiBmbHV4LlxuICogalF1ZXJ5KHdpbmRvdykub24oJ3Jlc2l6ZScsIF8uZGVib3VuY2UoY2FsY3VsYXRlTGF5b3V0LCAxNTApKTtcbiAqXG4gKiAvLyBJbnZva2UgYHNlbmRNYWlsYCB3aGVuIGNsaWNrZWQsIGRlYm91bmNpbmcgc3Vic2VxdWVudCBjYWxscy5cbiAqIGpRdWVyeShlbGVtZW50KS5vbignY2xpY2snLCBfLmRlYm91bmNlKHNlbmRNYWlsLCAzMDAsIHtcbiAqICAgJ2xlYWRpbmcnOiB0cnVlLFxuICogICAndHJhaWxpbmcnOiBmYWxzZVxuICogfSkpO1xuICpcbiAqIC8vIEVuc3VyZSBgYmF0Y2hMb2dgIGlzIGludm9rZWQgb25jZSBhZnRlciAxIHNlY29uZCBvZiBkZWJvdW5jZWQgY2FsbHMuXG4gKiB2YXIgZGVib3VuY2VkID0gXy5kZWJvdW5jZShiYXRjaExvZywgMjUwLCB7ICdtYXhXYWl0JzogMTAwMCB9KTtcbiAqIHZhciBzb3VyY2UgPSBuZXcgRXZlbnRTb3VyY2UoJy9zdHJlYW0nKTtcbiAqIGpRdWVyeShzb3VyY2UpLm9uKCdtZXNzYWdlJywgZGVib3VuY2VkKTtcbiAqXG4gKiAvLyBDYW5jZWwgdGhlIHRyYWlsaW5nIGRlYm91bmNlZCBpbnZvY2F0aW9uLlxuICogalF1ZXJ5KHdpbmRvdykub24oJ3BvcHN0YXRlJywgZGVib3VuY2VkLmNhbmNlbCk7XG4gKi9cbmZ1bmN0aW9uIGRlYm91bmNlKGZ1bmMsIHdhaXQsIG9wdGlvbnMpIHtcbiAgdmFyIGxhc3RBcmdzLFxuICAgICAgbGFzdFRoaXMsXG4gICAgICBtYXhXYWl0LFxuICAgICAgcmVzdWx0LFxuICAgICAgdGltZXJJZCxcbiAgICAgIGxhc3RDYWxsVGltZSxcbiAgICAgIGxhc3RJbnZva2VUaW1lID0gMCxcbiAgICAgIGxlYWRpbmcgPSBmYWxzZSxcbiAgICAgIG1heGluZyA9IGZhbHNlLFxuICAgICAgdHJhaWxpbmcgPSB0cnVlO1xuXG4gIGlmICh0eXBlb2YgZnVuYyAhPSAnZnVuY3Rpb24nKSB7XG4gICAgdGhyb3cgbmV3IFR5cGVFcnJvcihGVU5DX0VSUk9SX1RFWFQpO1xuICB9XG4gIHdhaXQgPSB0b051bWJlcih3YWl0KSB8fCAwO1xuICBpZiAoaXNPYmplY3Qob3B0aW9ucykpIHtcbiAgICBsZWFkaW5nID0gISFvcHRpb25zLmxlYWRpbmc7XG4gICAgbWF4aW5nID0gJ21heFdhaXQnIGluIG9wdGlvbnM7XG4gICAgbWF4V2FpdCA9IG1heGluZyA/IG5hdGl2ZU1heCh0b051bWJlcihvcHRpb25zLm1heFdhaXQpIHx8IDAsIHdhaXQpIDogbWF4V2FpdDtcbiAgICB0cmFpbGluZyA9ICd0cmFpbGluZycgaW4gb3B0aW9ucyA/ICEhb3B0aW9ucy50cmFpbGluZyA6IHRyYWlsaW5nO1xuICB9XG5cbiAgZnVuY3Rpb24gaW52b2tlRnVuYyh0aW1lKSB7XG4gICAgdmFyIGFyZ3MgPSBsYXN0QXJncyxcbiAgICAgICAgdGhpc0FyZyA9IGxhc3RUaGlzO1xuXG4gICAgbGFzdEFyZ3MgPSBsYXN0VGhpcyA9IHVuZGVmaW5lZDtcbiAgICBsYXN0SW52b2tlVGltZSA9IHRpbWU7XG4gICAgcmVzdWx0ID0gZnVuYy5hcHBseSh0aGlzQXJnLCBhcmdzKTtcbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG5cbiAgZnVuY3Rpb24gbGVhZGluZ0VkZ2UodGltZSkge1xuICAgIC8vIFJlc2V0IGFueSBgbWF4V2FpdGAgdGltZXIuXG4gICAgbGFzdEludm9rZVRpbWUgPSB0aW1lO1xuICAgIC8vIFN0YXJ0IHRoZSB0aW1lciBmb3IgdGhlIHRyYWlsaW5nIGVkZ2UuXG4gICAgdGltZXJJZCA9IHNldFRpbWVvdXQodGltZXJFeHBpcmVkLCB3YWl0KTtcbiAgICAvLyBJbnZva2UgdGhlIGxlYWRpbmcgZWRnZS5cbiAgICByZXR1cm4gbGVhZGluZyA/IGludm9rZUZ1bmModGltZSkgOiByZXN1bHQ7XG4gIH1cblxuICBmdW5jdGlvbiByZW1haW5pbmdXYWl0KHRpbWUpIHtcbiAgICB2YXIgdGltZVNpbmNlTGFzdENhbGwgPSB0aW1lIC0gbGFzdENhbGxUaW1lLFxuICAgICAgICB0aW1lU2luY2VMYXN0SW52b2tlID0gdGltZSAtIGxhc3RJbnZva2VUaW1lLFxuICAgICAgICByZXN1bHQgPSB3YWl0IC0gdGltZVNpbmNlTGFzdENhbGw7XG5cbiAgICByZXR1cm4gbWF4aW5nID8gbmF0aXZlTWluKHJlc3VsdCwgbWF4V2FpdCAtIHRpbWVTaW5jZUxhc3RJbnZva2UpIDogcmVzdWx0O1xuICB9XG5cbiAgZnVuY3Rpb24gc2hvdWxkSW52b2tlKHRpbWUpIHtcbiAgICB2YXIgdGltZVNpbmNlTGFzdENhbGwgPSB0aW1lIC0gbGFzdENhbGxUaW1lLFxuICAgICAgICB0aW1lU2luY2VMYXN0SW52b2tlID0gdGltZSAtIGxhc3RJbnZva2VUaW1lO1xuXG4gICAgLy8gRWl0aGVyIHRoaXMgaXMgdGhlIGZpcnN0IGNhbGwsIGFjdGl2aXR5IGhhcyBzdG9wcGVkIGFuZCB3ZSdyZSBhdCB0aGVcbiAgICAvLyB0cmFpbGluZyBlZGdlLCB0aGUgc3lzdGVtIHRpbWUgaGFzIGdvbmUgYmFja3dhcmRzIGFuZCB3ZSdyZSB0cmVhdGluZ1xuICAgIC8vIGl0IGFzIHRoZSB0cmFpbGluZyBlZGdlLCBvciB3ZSd2ZSBoaXQgdGhlIGBtYXhXYWl0YCBsaW1pdC5cbiAgICByZXR1cm4gKGxhc3RDYWxsVGltZSA9PT0gdW5kZWZpbmVkIHx8ICh0aW1lU2luY2VMYXN0Q2FsbCA+PSB3YWl0KSB8fFxuICAgICAgKHRpbWVTaW5jZUxhc3RDYWxsIDwgMCkgfHwgKG1heGluZyAmJiB0aW1lU2luY2VMYXN0SW52b2tlID49IG1heFdhaXQpKTtcbiAgfVxuXG4gIGZ1bmN0aW9uIHRpbWVyRXhwaXJlZCgpIHtcbiAgICB2YXIgdGltZSA9IG5vdygpO1xuICAgIGlmIChzaG91bGRJbnZva2UodGltZSkpIHtcbiAgICAgIHJldHVybiB0cmFpbGluZ0VkZ2UodGltZSk7XG4gICAgfVxuICAgIC8vIFJlc3RhcnQgdGhlIHRpbWVyLlxuICAgIHRpbWVySWQgPSBzZXRUaW1lb3V0KHRpbWVyRXhwaXJlZCwgcmVtYWluaW5nV2FpdCh0aW1lKSk7XG4gIH1cblxuICBmdW5jdGlvbiB0cmFpbGluZ0VkZ2UodGltZSkge1xuICAgIHRpbWVySWQgPSB1bmRlZmluZWQ7XG5cbiAgICAvLyBPbmx5IGludm9rZSBpZiB3ZSBoYXZlIGBsYXN0QXJnc2Agd2hpY2ggbWVhbnMgYGZ1bmNgIGhhcyBiZWVuXG4gICAgLy8gZGVib3VuY2VkIGF0IGxlYXN0IG9uY2UuXG4gICAgaWYgKHRyYWlsaW5nICYmIGxhc3RBcmdzKSB7XG4gICAgICByZXR1cm4gaW52b2tlRnVuYyh0aW1lKTtcbiAgICB9XG4gICAgbGFzdEFyZ3MgPSBsYXN0VGhpcyA9IHVuZGVmaW5lZDtcbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG5cbiAgZnVuY3Rpb24gY2FuY2VsKCkge1xuICAgIGlmICh0aW1lcklkICE9PSB1bmRlZmluZWQpIHtcbiAgICAgIGNsZWFyVGltZW91dCh0aW1lcklkKTtcbiAgICB9XG4gICAgbGFzdEludm9rZVRpbWUgPSAwO1xuICAgIGxhc3RBcmdzID0gbGFzdENhbGxUaW1lID0gbGFzdFRoaXMgPSB0aW1lcklkID0gdW5kZWZpbmVkO1xuICB9XG5cbiAgZnVuY3Rpb24gZmx1c2goKSB7XG4gICAgcmV0dXJuIHRpbWVySWQgPT09IHVuZGVmaW5lZCA/IHJlc3VsdCA6IHRyYWlsaW5nRWRnZShub3coKSk7XG4gIH1cblxuICBmdW5jdGlvbiBkZWJvdW5jZWQoKSB7XG4gICAgdmFyIHRpbWUgPSBub3coKSxcbiAgICAgICAgaXNJbnZva2luZyA9IHNob3VsZEludm9rZSh0aW1lKTtcblxuICAgIGxhc3RBcmdzID0gYXJndW1lbnRzO1xuICAgIGxhc3RUaGlzID0gdGhpcztcbiAgICBsYXN0Q2FsbFRpbWUgPSB0aW1lO1xuXG4gICAgaWYgKGlzSW52b2tpbmcpIHtcbiAgICAgIGlmICh0aW1lcklkID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgcmV0dXJuIGxlYWRpbmdFZGdlKGxhc3RDYWxsVGltZSk7XG4gICAgICB9XG4gICAgICBpZiAobWF4aW5nKSB7XG4gICAgICAgIC8vIEhhbmRsZSBpbnZvY2F0aW9ucyBpbiBhIHRpZ2h0IGxvb3AuXG4gICAgICAgIHRpbWVySWQgPSBzZXRUaW1lb3V0KHRpbWVyRXhwaXJlZCwgd2FpdCk7XG4gICAgICAgIHJldHVybiBpbnZva2VGdW5jKGxhc3RDYWxsVGltZSk7XG4gICAgICB9XG4gICAgfVxuICAgIGlmICh0aW1lcklkID09PSB1bmRlZmluZWQpIHtcbiAgICAgIHRpbWVySWQgPSBzZXRUaW1lb3V0KHRpbWVyRXhwaXJlZCwgd2FpdCk7XG4gICAgfVxuICAgIHJldHVybiByZXN1bHQ7XG4gIH1cbiAgZGVib3VuY2VkLmNhbmNlbCA9IGNhbmNlbDtcbiAgZGVib3VuY2VkLmZsdXNoID0gZmx1c2g7XG4gIHJldHVybiBkZWJvdW5jZWQ7XG59XG5cbi8qKlxuICogQ3JlYXRlcyBhIHRocm90dGxlZCBmdW5jdGlvbiB0aGF0IG9ubHkgaW52b2tlcyBgZnVuY2AgYXQgbW9zdCBvbmNlIHBlclxuICogZXZlcnkgYHdhaXRgIG1pbGxpc2Vjb25kcy4gVGhlIHRocm90dGxlZCBmdW5jdGlvbiBjb21lcyB3aXRoIGEgYGNhbmNlbGBcbiAqIG1ldGhvZCB0byBjYW5jZWwgZGVsYXllZCBgZnVuY2AgaW52b2NhdGlvbnMgYW5kIGEgYGZsdXNoYCBtZXRob2QgdG9cbiAqIGltbWVkaWF0ZWx5IGludm9rZSB0aGVtLiBQcm92aWRlIGBvcHRpb25zYCB0byBpbmRpY2F0ZSB3aGV0aGVyIGBmdW5jYFxuICogc2hvdWxkIGJlIGludm9rZWQgb24gdGhlIGxlYWRpbmcgYW5kL29yIHRyYWlsaW5nIGVkZ2Ugb2YgdGhlIGB3YWl0YFxuICogdGltZW91dC4gVGhlIGBmdW5jYCBpcyBpbnZva2VkIHdpdGggdGhlIGxhc3QgYXJndW1lbnRzIHByb3ZpZGVkIHRvIHRoZVxuICogdGhyb3R0bGVkIGZ1bmN0aW9uLiBTdWJzZXF1ZW50IGNhbGxzIHRvIHRoZSB0aHJvdHRsZWQgZnVuY3Rpb24gcmV0dXJuIHRoZVxuICogcmVzdWx0IG9mIHRoZSBsYXN0IGBmdW5jYCBpbnZvY2F0aW9uLlxuICpcbiAqICoqTm90ZToqKiBJZiBgbGVhZGluZ2AgYW5kIGB0cmFpbGluZ2Agb3B0aW9ucyBhcmUgYHRydWVgLCBgZnVuY2AgaXNcbiAqIGludm9rZWQgb24gdGhlIHRyYWlsaW5nIGVkZ2Ugb2YgdGhlIHRpbWVvdXQgb25seSBpZiB0aGUgdGhyb3R0bGVkIGZ1bmN0aW9uXG4gKiBpcyBpbnZva2VkIG1vcmUgdGhhbiBvbmNlIGR1cmluZyB0aGUgYHdhaXRgIHRpbWVvdXQuXG4gKlxuICogSWYgYHdhaXRgIGlzIGAwYCBhbmQgYGxlYWRpbmdgIGlzIGBmYWxzZWAsIGBmdW5jYCBpbnZvY2F0aW9uIGlzIGRlZmVycmVkXG4gKiB1bnRpbCB0byB0aGUgbmV4dCB0aWNrLCBzaW1pbGFyIHRvIGBzZXRUaW1lb3V0YCB3aXRoIGEgdGltZW91dCBvZiBgMGAuXG4gKlxuICogU2VlIFtEYXZpZCBDb3JiYWNobydzIGFydGljbGVdKGh0dHBzOi8vY3NzLXRyaWNrcy5jb20vZGVib3VuY2luZy10aHJvdHRsaW5nLWV4cGxhaW5lZC1leGFtcGxlcy8pXG4gKiBmb3IgZGV0YWlscyBvdmVyIHRoZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGBfLnRocm90dGxlYCBhbmQgYF8uZGVib3VuY2VgLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgMC4xLjBcbiAqIEBjYXRlZ29yeSBGdW5jdGlvblxuICogQHBhcmFtIHtGdW5jdGlvbn0gZnVuYyBUaGUgZnVuY3Rpb24gdG8gdGhyb3R0bGUuXG4gKiBAcGFyYW0ge251bWJlcn0gW3dhaXQ9MF0gVGhlIG51bWJlciBvZiBtaWxsaXNlY29uZHMgdG8gdGhyb3R0bGUgaW52b2NhdGlvbnMgdG8uXG4gKiBAcGFyYW0ge09iamVjdH0gW29wdGlvbnM9e31dIFRoZSBvcHRpb25zIG9iamVjdC5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW29wdGlvbnMubGVhZGluZz10cnVlXVxuICogIFNwZWNpZnkgaW52b2tpbmcgb24gdGhlIGxlYWRpbmcgZWRnZSBvZiB0aGUgdGltZW91dC5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW29wdGlvbnMudHJhaWxpbmc9dHJ1ZV1cbiAqICBTcGVjaWZ5IGludm9raW5nIG9uIHRoZSB0cmFpbGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0LlxuICogQHJldHVybnMge0Z1bmN0aW9ufSBSZXR1cm5zIHRoZSBuZXcgdGhyb3R0bGVkIGZ1bmN0aW9uLlxuICogQGV4YW1wbGVcbiAqXG4gKiAvLyBBdm9pZCBleGNlc3NpdmVseSB1cGRhdGluZyB0aGUgcG9zaXRpb24gd2hpbGUgc2Nyb2xsaW5nLlxuICogalF1ZXJ5KHdpbmRvdykub24oJ3Njcm9sbCcsIF8udGhyb3R0bGUodXBkYXRlUG9zaXRpb24sIDEwMCkpO1xuICpcbiAqIC8vIEludm9rZSBgcmVuZXdUb2tlbmAgd2hlbiB0aGUgY2xpY2sgZXZlbnQgaXMgZmlyZWQsIGJ1dCBub3QgbW9yZSB0aGFuIG9uY2UgZXZlcnkgNSBtaW51dGVzLlxuICogdmFyIHRocm90dGxlZCA9IF8udGhyb3R0bGUocmVuZXdUb2tlbiwgMzAwMDAwLCB7ICd0cmFpbGluZyc6IGZhbHNlIH0pO1xuICogalF1ZXJ5KGVsZW1lbnQpLm9uKCdjbGljaycsIHRocm90dGxlZCk7XG4gKlxuICogLy8gQ2FuY2VsIHRoZSB0cmFpbGluZyB0aHJvdHRsZWQgaW52b2NhdGlvbi5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdwb3BzdGF0ZScsIHRocm90dGxlZC5jYW5jZWwpO1xuICovXG5mdW5jdGlvbiB0aHJvdHRsZShmdW5jLCB3YWl0LCBvcHRpb25zKSB7XG4gIHZhciBsZWFkaW5nID0gdHJ1ZSxcbiAgICAgIHRyYWlsaW5nID0gdHJ1ZTtcblxuICBpZiAodHlwZW9mIGZ1bmMgIT0gJ2Z1bmN0aW9uJykge1xuICAgIHRocm93IG5ldyBUeXBlRXJyb3IoRlVOQ19FUlJPUl9URVhUKTtcbiAgfVxuICBpZiAoaXNPYmplY3Qob3B0aW9ucykpIHtcbiAgICBsZWFkaW5nID0gJ2xlYWRpbmcnIGluIG9wdGlvbnMgPyAhIW9wdGlvbnMubGVhZGluZyA6IGxlYWRpbmc7XG4gICAgdHJhaWxpbmcgPSAndHJhaWxpbmcnIGluIG9wdGlvbnMgPyAhIW9wdGlvbnMudHJhaWxpbmcgOiB0cmFpbGluZztcbiAgfVxuICByZXR1cm4gZGVib3VuY2UoZnVuYywgd2FpdCwge1xuICAgICdsZWFkaW5nJzogbGVhZGluZyxcbiAgICAnbWF4V2FpdCc6IHdhaXQsXG4gICAgJ3RyYWlsaW5nJzogdHJhaWxpbmdcbiAgfSk7XG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIGB2YWx1ZWAgaXMgdGhlXG4gKiBbbGFuZ3VhZ2UgdHlwZV0oaHR0cDovL3d3dy5lY21hLWludGVybmF0aW9uYWwub3JnL2VjbWEtMjYyLzcuMC8jc2VjLWVjbWFzY3JpcHQtbGFuZ3VhZ2UtdHlwZXMpXG4gKiBvZiBgT2JqZWN0YC4gKGUuZy4gYXJyYXlzLCBmdW5jdGlvbnMsIG9iamVjdHMsIHJlZ2V4ZXMsIGBuZXcgTnVtYmVyKDApYCwgYW5kIGBuZXcgU3RyaW5nKCcnKWApXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBzaW5jZSAwLjEuMFxuICogQGNhdGVnb3J5IExhbmdcbiAqIEBwYXJhbSB7Kn0gdmFsdWUgVGhlIHZhbHVlIHRvIGNoZWNrLlxuICogQHJldHVybnMge2Jvb2xlYW59IFJldHVybnMgYHRydWVgIGlmIGB2YWx1ZWAgaXMgYW4gb2JqZWN0LCBlbHNlIGBmYWxzZWAuXG4gKiBAZXhhbXBsZVxuICpcbiAqIF8uaXNPYmplY3Qoe30pO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNPYmplY3QoWzEsIDIsIDNdKTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0KF8ubm9vcCk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc09iamVjdChudWxsKTtcbiAqIC8vID0+IGZhbHNlXG4gKi9cbmZ1bmN0aW9uIGlzT2JqZWN0KHZhbHVlKSB7XG4gIHZhciB0eXBlID0gdHlwZW9mIHZhbHVlO1xuICByZXR1cm4gISF2YWx1ZSAmJiAodHlwZSA9PSAnb2JqZWN0JyB8fCB0eXBlID09ICdmdW5jdGlvbicpO1xufVxuXG4vKipcbiAqIENoZWNrcyBpZiBgdmFsdWVgIGlzIG9iamVjdC1saWtlLiBBIHZhbHVlIGlzIG9iamVjdC1saWtlIGlmIGl0J3Mgbm90IGBudWxsYFxuICogYW5kIGhhcyBhIGB0eXBlb2ZgIHJlc3VsdCBvZiBcIm9iamVjdFwiLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgNC4wLjBcbiAqIEBjYXRlZ29yeSBMYW5nXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBjaGVjay5cbiAqIEByZXR1cm5zIHtib29sZWFufSBSZXR1cm5zIGB0cnVlYCBpZiBgdmFsdWVgIGlzIG9iamVjdC1saWtlLCBlbHNlIGBmYWxzZWAuXG4gKiBAZXhhbXBsZVxuICpcbiAqIF8uaXNPYmplY3RMaWtlKHt9KTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZShbMSwgMiwgM10pO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNPYmplY3RMaWtlKF8ubm9vcCk7XG4gKiAvLyA9PiBmYWxzZVxuICpcbiAqIF8uaXNPYmplY3RMaWtlKG51bGwpO1xuICogLy8gPT4gZmFsc2VcbiAqL1xuZnVuY3Rpb24gaXNPYmplY3RMaWtlKHZhbHVlKSB7XG4gIHJldHVybiAhIXZhbHVlICYmIHR5cGVvZiB2YWx1ZSA9PSAnb2JqZWN0Jztcbn1cblxuLyoqXG4gKiBDaGVja3MgaWYgYHZhbHVlYCBpcyBjbGFzc2lmaWVkIGFzIGEgYFN5bWJvbGAgcHJpbWl0aXZlIG9yIG9iamVjdC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDQuMC4wXG4gKiBAY2F0ZWdvcnkgTGFuZ1xuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gY2hlY2suXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gUmV0dXJucyBgdHJ1ZWAgaWYgYHZhbHVlYCBpcyBhIHN5bWJvbCwgZWxzZSBgZmFsc2VgLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmlzU3ltYm9sKFN5bWJvbC5pdGVyYXRvcik7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc1N5bWJvbCgnYWJjJyk7XG4gKiAvLyA9PiBmYWxzZVxuICovXG5mdW5jdGlvbiBpc1N5bWJvbCh2YWx1ZSkge1xuICByZXR1cm4gdHlwZW9mIHZhbHVlID09ICdzeW1ib2wnIHx8XG4gICAgKGlzT2JqZWN0TGlrZSh2YWx1ZSkgJiYgb2JqZWN0VG9TdHJpbmcuY2FsbCh2YWx1ZSkgPT0gc3ltYm9sVGFnKTtcbn1cblxuLyoqXG4gKiBDb252ZXJ0cyBgdmFsdWVgIHRvIGEgbnVtYmVyLlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgNC4wLjBcbiAqIEBjYXRlZ29yeSBMYW5nXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBwcm9jZXNzLlxuICogQHJldHVybnMge251bWJlcn0gUmV0dXJucyB0aGUgbnVtYmVyLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLnRvTnVtYmVyKDMuMik7XG4gKiAvLyA9PiAzLjJcbiAqXG4gKiBfLnRvTnVtYmVyKE51bWJlci5NSU5fVkFMVUUpO1xuICogLy8gPT4gNWUtMzI0XG4gKlxuICogXy50b051bWJlcihJbmZpbml0eSk7XG4gKiAvLyA9PiBJbmZpbml0eVxuICpcbiAqIF8udG9OdW1iZXIoJzMuMicpO1xuICogLy8gPT4gMy4yXG4gKi9cbmZ1bmN0aW9uIHRvTnVtYmVyKHZhbHVlKSB7XG4gIGlmICh0eXBlb2YgdmFsdWUgPT0gJ251bWJlcicpIHtcbiAgICByZXR1cm4gdmFsdWU7XG4gIH1cbiAgaWYgKGlzU3ltYm9sKHZhbHVlKSkge1xuICAgIHJldHVybiBOQU47XG4gIH1cbiAgaWYgKGlzT2JqZWN0KHZhbHVlKSkge1xuICAgIHZhciBvdGhlciA9IHR5cGVvZiB2YWx1ZS52YWx1ZU9mID09ICdmdW5jdGlvbicgPyB2YWx1ZS52YWx1ZU9mKCkgOiB2YWx1ZTtcbiAgICB2YWx1ZSA9IGlzT2JqZWN0KG90aGVyKSA/IChvdGhlciArICcnKSA6IG90aGVyO1xuICB9XG4gIGlmICh0eXBlb2YgdmFsdWUgIT0gJ3N0cmluZycpIHtcbiAgICByZXR1cm4gdmFsdWUgPT09IDAgPyB2YWx1ZSA6ICt2YWx1ZTtcbiAgfVxuICB2YWx1ZSA9IHZhbHVlLnJlcGxhY2UocmVUcmltLCAnJyk7XG4gIHZhciBpc0JpbmFyeSA9IHJlSXNCaW5hcnkudGVzdCh2YWx1ZSk7XG4gIHJldHVybiAoaXNCaW5hcnkgfHwgcmVJc09jdGFsLnRlc3QodmFsdWUpKVxuICAgID8gZnJlZVBhcnNlSW50KHZhbHVlLnNsaWNlKDIpLCBpc0JpbmFyeSA/IDIgOiA4KVxuICAgIDogKHJlSXNCYWRIZXgudGVzdCh2YWx1ZSkgPyBOQU4gOiArdmFsdWUpO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IHRocm90dGxlO1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwgeyB2YWx1ZTogdHJ1ZSB9KTtcbmNvbnN0IHNldHRpbmdzXzEgPSByZXF1aXJlKFwiLi9zZXR0aW5nc1wiKTtcbmV4cG9ydHMuY3JlYXRlUG9zdGVyRm9yVnNDb2RlID0gKHZzY29kZSkgPT4ge1xuICAgIHJldHVybiBuZXcgY2xhc3Mge1xuICAgICAgICBwb3N0TWVzc2FnZSh0eXBlLCBib2R5KSB7XG4gICAgICAgICAgICB2c2NvZGUucG9zdE1lc3NhZ2Uoe1xuICAgICAgICAgICAgICAgIHR5cGUsXG4gICAgICAgICAgICAgICAgc291cmNlOiBzZXR0aW5nc18xLmdldFNldHRpbmdzKCkuc291cmNlLFxuICAgICAgICAgICAgICAgIGJvZHlcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgfTtcbn07XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuZnVuY3Rpb24gb25jZURvY3VtZW50TG9hZGVkKGYpIHtcbiAgICBpZiAoZG9jdW1lbnQucmVhZHlTdGF0ZSA9PT0gJ2xvYWRpbmcnIHx8IGRvY3VtZW50LnJlYWR5U3RhdGUgPT09ICd1bmluaXRpYWxpemVkJykge1xuICAgICAgICBkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdET01Db250ZW50TG9hZGVkJywgZik7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBmKCk7XG4gICAgfVxufVxuZXhwb3J0cy5vbmNlRG9jdW1lbnRMb2FkZWQgPSBvbmNlRG9jdW1lbnRMb2FkZWQ7XG4iLCJcInVzZSBzdHJpY3RcIjtcbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwgeyB2YWx1ZTogdHJ1ZSB9KTtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuY29uc3Qgc2Nyb2xsX3N5bmNfMSA9IHJlcXVpcmUoXCIuL3Njcm9sbC1zeW5jXCIpO1xuY2xhc3MgQWN0aXZlTGluZU1hcmtlciB7XG4gICAgb25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uKGxpbmUpIHtcbiAgICAgICAgY29uc3QgeyBwcmV2aW91cyB9ID0gc2Nyb2xsX3N5bmNfMS5nZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUobGluZSk7XG4gICAgICAgIHRoaXMuX3VwZGF0ZShwcmV2aW91cyAmJiBwcmV2aW91cy5lbGVtZW50KTtcbiAgICB9XG4gICAgX3VwZGF0ZShiZWZvcmUpIHtcbiAgICAgICAgdGhpcy5fdW5tYXJrQWN0aXZlRWxlbWVudCh0aGlzLl9jdXJyZW50KTtcbiAgICAgICAgdGhpcy5fbWFya0FjdGl2ZUVsZW1lbnQoYmVmb3JlKTtcbiAgICAgICAgdGhpcy5fY3VycmVudCA9IGJlZm9yZTtcbiAgICB9XG4gICAgX3VubWFya0FjdGl2ZUVsZW1lbnQoZWxlbWVudCkge1xuICAgICAgICBpZiAoIWVsZW1lbnQpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBlbGVtZW50LmNsYXNzTmFtZSA9IGVsZW1lbnQuY2xhc3NOYW1lLnJlcGxhY2UoL1xcYmNvZGUtYWN0aXZlLWxpbmVcXGIvZywgJycpO1xuICAgIH1cbiAgICBfbWFya0FjdGl2ZUVsZW1lbnQoZWxlbWVudCkge1xuICAgICAgICBpZiAoIWVsZW1lbnQpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBlbGVtZW50LmNsYXNzTmFtZSArPSAnIGNvZGUtYWN0aXZlLWxpbmUnO1xuICAgIH1cbn1cbmV4cG9ydHMuQWN0aXZlTGluZU1hcmtlciA9IEFjdGl2ZUxpbmVNYXJrZXI7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuY29uc3QgYWN0aXZlTGluZU1hcmtlcl8xID0gcmVxdWlyZShcIi4vYWN0aXZlTGluZU1hcmtlclwiKTtcbmNvbnN0IGV2ZW50c18xID0gcmVxdWlyZShcIi4vZXZlbnRzXCIpO1xuY29uc3QgbWVzc2FnaW5nXzEgPSByZXF1aXJlKFwiLi9tZXNzYWdpbmdcIik7XG5jb25zdCBzY3JvbGxfc3luY18xID0gcmVxdWlyZShcIi4vc2Nyb2xsLXN5bmNcIik7XG5jb25zdCBzZXR0aW5nc18xID0gcmVxdWlyZShcIi4vc2V0dGluZ3NcIik7XG5jb25zdCB0aHJvdHRsZSA9IHJlcXVpcmUoXCJsb2Rhc2gudGhyb3R0bGVcIik7XG5sZXQgc2Nyb2xsRGlzYWJsZWQgPSB0cnVlO1xuY29uc3QgbWFya2VyID0gbmV3IGFjdGl2ZUxpbmVNYXJrZXJfMS5BY3RpdmVMaW5lTWFya2VyKCk7XG5jb25zdCBzZXR0aW5ncyA9IHNldHRpbmdzXzEuZ2V0U2V0dGluZ3MoKTtcbmNvbnN0IHZzY29kZSA9IGFjcXVpcmVWc0NvZGVBcGkoKTtcbi8vIFNldCBWUyBDb2RlIHN0YXRlXG5sZXQgc3RhdGUgPSBzZXR0aW5nc18xLmdldERhdGEoJ2RhdGEtc3RhdGUnKTtcbnZzY29kZS5zZXRTdGF0ZShzdGF0ZSk7XG5jb25zdCBtZXNzYWdpbmcgPSBtZXNzYWdpbmdfMS5jcmVhdGVQb3N0ZXJGb3JWc0NvZGUodnNjb2RlKTtcbndpbmRvdy5jc3BBbGVydGVyLnNldFBvc3RlcihtZXNzYWdpbmcpO1xud2luZG93LnN0eWxlTG9hZGluZ01vbml0b3Iuc2V0UG9zdGVyKG1lc3NhZ2luZyk7XG53aW5kb3cub25sb2FkID0gKCkgPT4ge1xuICAgIHVwZGF0ZUltYWdlU2l6ZXMoKTtcbn07XG5ldmVudHNfMS5vbmNlRG9jdW1lbnRMb2FkZWQoKCkgPT4ge1xuICAgIGlmIChzZXR0aW5ncy5zY3JvbGxQcmV2aWV3V2l0aEVkaXRvcikge1xuICAgICAgICBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgICAgICAgIC8vIFRyeSB0byBzY3JvbGwgdG8gZnJhZ21lbnQgaWYgYXZhaWxhYmxlXG4gICAgICAgICAgICBpZiAoc3RhdGUuZnJhZ21lbnQpIHtcbiAgICAgICAgICAgICAgICBjb25zdCBlbGVtZW50ID0gc2Nyb2xsX3N5bmNfMS5nZXRMaW5lRWxlbWVudEZvckZyYWdtZW50KHN0YXRlLmZyYWdtZW50KTtcbiAgICAgICAgICAgICAgICBpZiAoZWxlbWVudCkge1xuICAgICAgICAgICAgICAgICAgICBzY3JvbGxEaXNhYmxlZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgIHNjcm9sbF9zeW5jXzEuc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGVsZW1lbnQubGluZSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgY29uc3QgaW5pdGlhbExpbmUgPSArc2V0dGluZ3MubGluZTtcbiAgICAgICAgICAgICAgICBpZiAoIWlzTmFOKGluaXRpYWxMaW5lKSkge1xuICAgICAgICAgICAgICAgICAgICBzY3JvbGxEaXNhYmxlZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgIHNjcm9sbF9zeW5jXzEuc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGluaXRpYWxMaW5lKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH0sIDApO1xuICAgIH1cbn0pO1xuY29uc3Qgb25VcGRhdGVWaWV3ID0gKCgpID0+IHtcbiAgICBjb25zdCBkb1Njcm9sbCA9IHRocm90dGxlKChsaW5lKSA9PiB7XG4gICAgICAgIHNjcm9sbERpc2FibGVkID0gdHJ1ZTtcbiAgICAgICAgc2Nyb2xsX3N5bmNfMS5zY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUobGluZSk7XG4gICAgfSwgNTApO1xuICAgIHJldHVybiAobGluZSwgc2V0dGluZ3MpID0+IHtcbiAgICAgICAgaWYgKCFpc05hTihsaW5lKSkge1xuICAgICAgICAgICAgc2V0dGluZ3MubGluZSA9IGxpbmU7XG4gICAgICAgICAgICBkb1Njcm9sbChsaW5lKTtcbiAgICAgICAgfVxuICAgIH07XG59KSgpO1xubGV0IHVwZGF0ZUltYWdlU2l6ZXMgPSB0aHJvdHRsZSgoKSA9PiB7XG4gICAgY29uc3QgaW1hZ2VJbmZvID0gW107XG4gICAgbGV0IGltYWdlcyA9IGRvY3VtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCdpbWcnKTtcbiAgICBpZiAoaW1hZ2VzKSB7XG4gICAgICAgIGxldCBpO1xuICAgICAgICBmb3IgKGkgPSAwOyBpIDwgaW1hZ2VzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBjb25zdCBpbWcgPSBpbWFnZXNbaV07XG4gICAgICAgICAgICBpZiAoaW1nLmNsYXNzTGlzdC5jb250YWlucygnbG9hZGluZycpKSB7XG4gICAgICAgICAgICAgICAgaW1nLmNsYXNzTGlzdC5yZW1vdmUoJ2xvYWRpbmcnKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGltYWdlSW5mby5wdXNoKHtcbiAgICAgICAgICAgICAgICBpZDogaW1nLmlkLFxuICAgICAgICAgICAgICAgIGhlaWdodDogaW1nLmhlaWdodCxcbiAgICAgICAgICAgICAgICB3aWR0aDogaW1nLndpZHRoXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgICAgICBtZXNzYWdpbmcucG9zdE1lc3NhZ2UoJ2NhY2hlSW1hZ2VTaXplcycsIGltYWdlSW5mbyk7XG4gICAgfVxufSwgNTApO1xud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ3Jlc2l6ZScsICgpID0+IHtcbiAgICBzY3JvbGxEaXNhYmxlZCA9IHRydWU7XG4gICAgdXBkYXRlSW1hZ2VTaXplcygpO1xufSwgdHJ1ZSk7XG53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbWVzc2FnZScsIGV2ZW50ID0+IHtcbiAgICBpZiAoZXZlbnQuZGF0YS5zb3VyY2UgIT09IHNldHRpbmdzLnNvdXJjZSkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIHN3aXRjaCAoZXZlbnQuZGF0YS50eXBlKSB7XG4gICAgICAgIGNhc2UgJ29uRGlkQ2hhbmdlVGV4dEVkaXRvclNlbGVjdGlvbic6XG4gICAgICAgICAgICBtYXJrZXIub25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uKGV2ZW50LmRhdGEubGluZSk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSAndXBkYXRlVmlldyc6XG4gICAgICAgICAgICBvblVwZGF0ZVZpZXcoZXZlbnQuZGF0YS5saW5lLCBzZXR0aW5ncyk7XG4gICAgICAgICAgICBicmVhaztcbiAgICB9XG59LCBmYWxzZSk7XG5kb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdkYmxjbGljaycsIGV2ZW50ID0+IHtcbiAgICBpZiAoIXNldHRpbmdzLmRvdWJsZUNsaWNrVG9Td2l0Y2hUb0VkaXRvcikge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIC8vIElnbm9yZSBjbGlja3Mgb24gbGlua3NcbiAgICBmb3IgKGxldCBub2RlID0gZXZlbnQudGFyZ2V0OyBub2RlOyBub2RlID0gbm9kZS5wYXJlbnROb2RlKSB7XG4gICAgICAgIGlmIChub2RlLnRhZ05hbWUgPT09ICdBJykge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgfVxuICAgIGNvbnN0IG9mZnNldCA9IGV2ZW50LnBhZ2VZO1xuICAgIGNvbnN0IGxpbmUgPSBzY3JvbGxfc3luY18xLmdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0KG9mZnNldCk7XG4gICAgaWYgKHR5cGVvZiBsaW5lID09PSAnbnVtYmVyJyAmJiAhaXNOYU4obGluZSkpIHtcbiAgICAgICAgbWVzc2FnaW5nLnBvc3RNZXNzYWdlKCdkaWRDbGljaycsIHsgbGluZTogTWF0aC5mbG9vcihsaW5lKSB9KTtcbiAgICB9XG59KTtcbmNvbnN0IHBhc3NUaHJvdWdoTGlua1NjaGVtZXMgPSBbJ2h0dHA6JywgJ2h0dHBzOicsICdtYWlsdG86JywgJ3ZzY29kZTonLCAndnNjb2RlLWluc2lkZXJzJ107XG5kb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsIGV2ZW50ID0+IHtcbiAgICBpZiAoIWV2ZW50KSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgbGV0IG5vZGUgPSBldmVudC50YXJnZXQ7XG4gICAgd2hpbGUgKG5vZGUpIHtcbiAgICAgICAgaWYgKG5vZGUudGFnTmFtZSAmJiBub2RlLnRhZ05hbWUgPT09ICdBJyAmJiBub2RlLmhyZWYpIHtcbiAgICAgICAgICAgIGlmIChub2RlLmdldEF0dHJpYnV0ZSgnaHJlZicpLnN0YXJ0c1dpdGgoJyMnKSkge1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIFBhc3MgdGhyb3VnaCBrbm93biBzY2hlbWVzXG4gICAgICAgICAgICBpZiAocGFzc1Rocm91Z2hMaW5rU2NoZW1lcy5zb21lKHNjaGVtZSA9PiBub2RlLmhyZWYuc3RhcnRzV2l0aChzY2hlbWUpKSkge1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNvbnN0IGhyZWZUZXh0ID0gbm9kZS5nZXRBdHRyaWJ1dGUoJ2RhdGEtaHJlZicpIHx8IG5vZGUuZ2V0QXR0cmlidXRlKCdocmVmJyk7XG4gICAgICAgICAgICAvLyBJZiBvcmlnaW5hbCBsaW5rIGRvZXNuJ3QgbG9vayBsaWtlIGEgdXJsLCBkZWxlZ2F0ZSBiYWNrIHRvIFZTIENvZGUgdG8gcmVzb2x2ZVxuICAgICAgICAgICAgaWYgKCEvXlthLXpcXC1dKzovaS50ZXN0KGhyZWZUZXh0KSkge1xuICAgICAgICAgICAgICAgIG1lc3NhZ2luZy5wb3N0TWVzc2FnZSgnb3BlbkxpbmsnLCB7IGhyZWY6IGhyZWZUZXh0IH0pO1xuICAgICAgICAgICAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG4gICAgICAgICAgICAgICAgZXZlbnQuc3RvcFByb3BhZ2F0aW9uKCk7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIG5vZGUgPSBub2RlLnBhcmVudE5vZGU7XG4gICAgfVxufSwgdHJ1ZSk7XG53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignc2Nyb2xsJywgdGhyb3R0bGUoKCkgPT4ge1xuICAgIGlmIChzY3JvbGxEaXNhYmxlZCkge1xuICAgICAgICBzY3JvbGxEaXNhYmxlZCA9IGZhbHNlO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgY29uc3QgbGluZSA9IHNjcm9sbF9zeW5jXzEuZ2V0RWRpdG9yTGluZU51bWJlckZvclBhZ2VPZmZzZXQod2luZG93LnNjcm9sbFkpO1xuICAgICAgICBpZiAodHlwZW9mIGxpbmUgPT09ICdudW1iZXInICYmICFpc05hTihsaW5lKSkge1xuICAgICAgICAgICAgbWVzc2FnaW5nLnBvc3RNZXNzYWdlKCdyZXZlYWxMaW5lJywgeyBsaW5lIH0pO1xuICAgICAgICAgICAgc3RhdGUubGluZSA9IGxpbmU7XG4gICAgICAgICAgICB2c2NvZGUuc2V0U3RhdGUoc3RhdGUpO1xuICAgICAgICB9XG4gICAgfVxufSwgNTApKTtcbiJdLCJzb3VyY2VSb290IjoiIn0= diff --git a/extensions/markdown-language-features/media/pre.js b/extensions/markdown-language-features/media/pre.js index 69a8092393..2d0f917feb 100644 --- a/extensions/markdown-language-features/media/pre.js +++ b/extensions/markdown-language-features/media/pre.js @@ -1,280 +1,2 @@ -/******/ (function(modules) { // webpackBootstrap -/******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; -/******/ } -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/******/ -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; -/******/ -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; -/******/ -/******/ // define getter function for harmony exports -/******/ __webpack_require__.d = function(exports, name, getter) { -/******/ if(!__webpack_require__.o(exports, name)) { -/******/ Object.defineProperty(exports, name, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // getDefaultExport function for compatibility with non-harmony modules -/******/ __webpack_require__.n = function(module) { -/******/ var getter = module && module.__esModule ? -/******/ function getDefault() { return module['default']; } : -/******/ function getModuleExports() { return module; }; -/******/ __webpack_require__.d(getter, 'a', getter); -/******/ return getter; -/******/ }; -/******/ -/******/ // Object.prototype.hasOwnProperty.call -/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; -/******/ -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = ""; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = "./preview-src/pre.ts"); -/******/ }) -/************************************************************************/ -/******/ ({ - -/***/ "./preview-src/csp.ts": -/*!****************************!*\ - !*** ./preview-src/csp.ts ***! - \****************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -const settings_1 = __webpack_require__(/*! ./settings */ "./preview-src/settings.ts"); -const strings_1 = __webpack_require__(/*! ./strings */ "./preview-src/strings.ts"); -/** - * Shows an alert when there is a content security policy violation. - */ -class CspAlerter { - constructor() { - this.didShow = false; - this.didHaveCspWarning = false; - document.addEventListener('securitypolicyviolation', () => { - this.onCspWarning(); - }); - window.addEventListener('message', (event) => { - if (event && event.data && event.data.name === 'vscode-did-block-svg') { - this.onCspWarning(); - } - }); - } - setPoster(poster) { - this.messaging = poster; - if (this.didHaveCspWarning) { - this.showCspWarning(); - } - } - onCspWarning() { - this.didHaveCspWarning = true; - this.showCspWarning(); - } - showCspWarning() { - const strings = strings_1.getStrings(); - const settings = settings_1.getSettings(); - if (this.didShow || settings.disableSecurityWarnings || !this.messaging) { - return; - } - this.didShow = true; - const notification = document.createElement('a'); - notification.innerText = strings.cspAlertMessageText; - notification.setAttribute('id', 'code-csp-warning'); - notification.setAttribute('title', strings.cspAlertMessageTitle); - notification.setAttribute('role', 'button'); - notification.setAttribute('aria-label', strings.cspAlertMessageLabel); - notification.onclick = () => { - this.messaging.postMessage('showPreviewSecuritySelector', { source: settings.source }); - }; - document.body.appendChild(notification); - } -} -exports.CspAlerter = CspAlerter; - - -/***/ }), - -/***/ "./preview-src/loading.ts": -/*!********************************!*\ - !*** ./preview-src/loading.ts ***! - \********************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -class StyleLoadingMonitor { - constructor() { - this.unloadedStyles = []; - this.finishedLoading = false; - const onStyleLoadError = (event) => { - const source = event.target.dataset.source; - this.unloadedStyles.push(source); - }; - window.addEventListener('DOMContentLoaded', () => { - for (const link of document.getElementsByClassName('code-user-style')) { - if (link.dataset.source) { - link.onerror = onStyleLoadError; - } - } - }); - window.addEventListener('load', () => { - if (!this.unloadedStyles.length) { - return; - } - this.finishedLoading = true; - if (this.poster) { - this.poster.postMessage('previewStyleLoadError', { unloadedStyles: this.unloadedStyles }); - } - }); - } - setPoster(poster) { - this.poster = poster; - if (this.finishedLoading) { - poster.postMessage('previewStyleLoadError', { unloadedStyles: this.unloadedStyles }); - } - } -} -exports.StyleLoadingMonitor = StyleLoadingMonitor; - - -/***/ }), - -/***/ "./preview-src/pre.ts": -/*!****************************!*\ - !*** ./preview-src/pre.ts ***! - \****************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -const csp_1 = __webpack_require__(/*! ./csp */ "./preview-src/csp.ts"); -const loading_1 = __webpack_require__(/*! ./loading */ "./preview-src/loading.ts"); -window.cspAlerter = new csp_1.CspAlerter(); -window.styleLoadingMonitor = new loading_1.StyleLoadingMonitor(); - - -/***/ }), - -/***/ "./preview-src/settings.ts": -/*!*********************************!*\ - !*** ./preview-src/settings.ts ***! - \*********************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -let cachedSettings = undefined; -function getData(key) { - const element = document.getElementById('vscode-markdown-preview-data'); - if (element) { - const data = element.getAttribute(key); - if (data) { - return JSON.parse(data); - } - } - throw new Error(`Could not load data for ${key}`); -} -exports.getData = getData; -function getSettings() { - if (cachedSettings) { - return cachedSettings; - } - cachedSettings = getData('data-settings'); - if (cachedSettings) { - return cachedSettings; - } - throw new Error('Could not load settings'); -} -exports.getSettings = getSettings; - - -/***/ }), - -/***/ "./preview-src/strings.ts": -/*!********************************!*\ - !*** ./preview-src/strings.ts ***! - \********************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -function getStrings() { - const store = document.getElementById('vscode-markdown-preview-data'); - if (store) { - const data = store.getAttribute('data-strings'); - if (data) { - return JSON.parse(data); - } - } - throw new Error('Could not load strings'); -} -exports.getStrings = getStrings; - - -/***/ }) - -/******/ }); -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvY3NwLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2xvYWRpbmcudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvcHJlLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL3NldHRpbmdzLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL3N0cmluZ3MudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOzs7QUFHQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFLO0FBQ0w7QUFDQTs7QUFFQTtBQUNBO0FBQ0EseURBQWlELGNBQWM7QUFDL0Q7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsbUNBQTJCLDBCQUEwQixFQUFFO0FBQ3ZELHlDQUFpQyxlQUFlO0FBQ2hEO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLDhEQUFzRCwrREFBK0Q7O0FBRXJIO0FBQ0E7OztBQUdBO0FBQ0E7Ozs7Ozs7Ozs7Ozs7QUNuRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDhDQUE4QyxjQUFjO0FBQzVEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx1RUFBdUUsMEJBQTBCO0FBQ2pHO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7Ozs7Ozs7Ozs7Ozs7QUNyREE7QUFDQSw4Q0FBOEMsY0FBYztBQUM1RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtFQUFrRSxzQ0FBc0M7QUFDeEc7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQSx5REFBeUQsc0NBQXNDO0FBQy9GO0FBQ0E7QUFDQTtBQUNBOzs7Ozs7Ozs7Ozs7O0FDbENBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw4Q0FBOEMsY0FBYztBQUM1RDtBQUNBO0FBQ0E7QUFDQTs7Ozs7Ozs7Ozs7OztBQ1RBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw4Q0FBOEMsY0FBYztBQUM1RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwrQ0FBK0MsSUFBSTtBQUNuRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7Ozs7Ozs7Ozs7OztBQzVCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsOENBQThDLGNBQWM7QUFDNUQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJwcmUuanMiLCJzb3VyY2VzQ29udGVudCI6WyIgXHQvLyBUaGUgbW9kdWxlIGNhY2hlXG4gXHR2YXIgaW5zdGFsbGVkTW9kdWxlcyA9IHt9O1xuXG4gXHQvLyBUaGUgcmVxdWlyZSBmdW5jdGlvblxuIFx0ZnVuY3Rpb24gX193ZWJwYWNrX3JlcXVpcmVfXyhtb2R1bGVJZCkge1xuXG4gXHRcdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuIFx0XHRpZihpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSkge1xuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuIFx0XHR9XG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRpOiBtb2R1bGVJZCxcbiBcdFx0XHRsOiBmYWxzZSxcbiBcdFx0XHRleHBvcnRzOiB7fVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sID0gdHJ1ZTtcblxuIFx0XHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuIFx0XHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG4gXHR9XG5cblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXylcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubSA9IG1vZHVsZXM7XG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlIGNhY2hlXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmMgPSBpbnN0YWxsZWRNb2R1bGVzO1xuXG4gXHQvLyBkZWZpbmUgZ2V0dGVyIGZ1bmN0aW9uIGZvciBoYXJtb255IGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uZCA9IGZ1bmN0aW9uKGV4cG9ydHMsIG5hbWUsIGdldHRlcikge1xuIFx0XHRpZighX193ZWJwYWNrX3JlcXVpcmVfXy5vKGV4cG9ydHMsIG5hbWUpKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIG5hbWUsIHtcbiBcdFx0XHRcdGNvbmZpZ3VyYWJsZTogZmFsc2UsXG4gXHRcdFx0XHRlbnVtZXJhYmxlOiB0cnVlLFxuIFx0XHRcdFx0Z2V0OiBnZXR0ZXJcbiBcdFx0XHR9KTtcbiBcdFx0fVxuIFx0fTtcblxuIFx0Ly8gZGVmaW5lIF9fZXNNb2R1bGUgb24gZXhwb3J0c1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5yID0gZnVuY3Rpb24oZXhwb3J0cykge1xuIFx0XHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7IHZhbHVlOiB0cnVlIH0pO1xuIFx0fTtcblxuIFx0Ly8gZ2V0RGVmYXVsdEV4cG9ydCBmdW5jdGlvbiBmb3IgY29tcGF0aWJpbGl0eSB3aXRoIG5vbi1oYXJtb255IG1vZHVsZXNcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubiA9IGZ1bmN0aW9uKG1vZHVsZSkge1xuIFx0XHR2YXIgZ2V0dGVyID0gbW9kdWxlICYmIG1vZHVsZS5fX2VzTW9kdWxlID9cbiBcdFx0XHRmdW5jdGlvbiBnZXREZWZhdWx0KCkgeyByZXR1cm4gbW9kdWxlWydkZWZhdWx0J107IH0gOlxuIFx0XHRcdGZ1bmN0aW9uIGdldE1vZHVsZUV4cG9ydHMoKSB7IHJldHVybiBtb2R1bGU7IH07XG4gXHRcdF9fd2VicGFja19yZXF1aXJlX18uZChnZXR0ZXIsICdhJywgZ2V0dGVyKTtcbiBcdFx0cmV0dXJuIGdldHRlcjtcbiBcdH07XG5cbiBcdC8vIE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbFxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5vID0gZnVuY3Rpb24ob2JqZWN0LCBwcm9wZXJ0eSkgeyByZXR1cm4gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKG9iamVjdCwgcHJvcGVydHkpOyB9O1xuXG4gXHQvLyBfX3dlYnBhY2tfcHVibGljX3BhdGhfX1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5wID0gXCJcIjtcblxuXG4gXHQvLyBMb2FkIGVudHJ5IG1vZHVsZSBhbmQgcmV0dXJuIGV4cG9ydHNcbiBcdHJldHVybiBfX3dlYnBhY2tfcmVxdWlyZV9fKF9fd2VicGFja19yZXF1aXJlX18ucyA9IFwiLi9wcmV2aWV3LXNyYy9wcmUudHNcIik7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuY29uc3Qgc2V0dGluZ3NfMSA9IHJlcXVpcmUoXCIuL3NldHRpbmdzXCIpO1xuY29uc3Qgc3RyaW5nc18xID0gcmVxdWlyZShcIi4vc3RyaW5nc1wiKTtcbi8qKlxuICogU2hvd3MgYW4gYWxlcnQgd2hlbiB0aGVyZSBpcyBhIGNvbnRlbnQgc2VjdXJpdHkgcG9saWN5IHZpb2xhdGlvbi5cbiAqL1xuY2xhc3MgQ3NwQWxlcnRlciB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMuZGlkU2hvdyA9IGZhbHNlO1xuICAgICAgICB0aGlzLmRpZEhhdmVDc3BXYXJuaW5nID0gZmFsc2U7XG4gICAgICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ3NlY3VyaXR5cG9saWN5dmlvbGF0aW9uJywgKCkgPT4ge1xuICAgICAgICAgICAgdGhpcy5vbkNzcFdhcm5pbmcoKTtcbiAgICAgICAgfSk7XG4gICAgICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtZXNzYWdlJywgKGV2ZW50KSA9PiB7XG4gICAgICAgICAgICBpZiAoZXZlbnQgJiYgZXZlbnQuZGF0YSAmJiBldmVudC5kYXRhLm5hbWUgPT09ICd2c2NvZGUtZGlkLWJsb2NrLXN2ZycpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9uQ3NwV2FybmluZygpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICB9XG4gICAgc2V0UG9zdGVyKHBvc3Rlcikge1xuICAgICAgICB0aGlzLm1lc3NhZ2luZyA9IHBvc3RlcjtcbiAgICAgICAgaWYgKHRoaXMuZGlkSGF2ZUNzcFdhcm5pbmcpIHtcbiAgICAgICAgICAgIHRoaXMuc2hvd0NzcFdhcm5pbmcoKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICBvbkNzcFdhcm5pbmcoKSB7XG4gICAgICAgIHRoaXMuZGlkSGF2ZUNzcFdhcm5pbmcgPSB0cnVlO1xuICAgICAgICB0aGlzLnNob3dDc3BXYXJuaW5nKCk7XG4gICAgfVxuICAgIHNob3dDc3BXYXJuaW5nKCkge1xuICAgICAgICBjb25zdCBzdHJpbmdzID0gc3RyaW5nc18xLmdldFN0cmluZ3MoKTtcbiAgICAgICAgY29uc3Qgc2V0dGluZ3MgPSBzZXR0aW5nc18xLmdldFNldHRpbmdzKCk7XG4gICAgICAgIGlmICh0aGlzLmRpZFNob3cgfHwgc2V0dGluZ3MuZGlzYWJsZVNlY3VyaXR5V2FybmluZ3MgfHwgIXRoaXMubWVzc2FnaW5nKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5kaWRTaG93ID0gdHJ1ZTtcbiAgICAgICAgY29uc3Qgbm90aWZpY2F0aW9uID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnYScpO1xuICAgICAgICBub3RpZmljYXRpb24uaW5uZXJUZXh0ID0gc3RyaW5ncy5jc3BBbGVydE1lc3NhZ2VUZXh0O1xuICAgICAgICBub3RpZmljYXRpb24uc2V0QXR0cmlidXRlKCdpZCcsICdjb2RlLWNzcC13YXJuaW5nJyk7XG4gICAgICAgIG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ3RpdGxlJywgc3RyaW5ncy5jc3BBbGVydE1lc3NhZ2VUaXRsZSk7XG4gICAgICAgIG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ3JvbGUnLCAnYnV0dG9uJyk7XG4gICAgICAgIG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ2FyaWEtbGFiZWwnLCBzdHJpbmdzLmNzcEFsZXJ0TWVzc2FnZUxhYmVsKTtcbiAgICAgICAgbm90aWZpY2F0aW9uLm9uY2xpY2sgPSAoKSA9PiB7XG4gICAgICAgICAgICB0aGlzLm1lc3NhZ2luZy5wb3N0TWVzc2FnZSgnc2hvd1ByZXZpZXdTZWN1cml0eVNlbGVjdG9yJywgeyBzb3VyY2U6IHNldHRpbmdzLnNvdXJjZSB9KTtcbiAgICAgICAgfTtcbiAgICAgICAgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChub3RpZmljYXRpb24pO1xuICAgIH1cbn1cbmV4cG9ydHMuQ3NwQWxlcnRlciA9IENzcEFsZXJ0ZXI7XG4iLCJcInVzZSBzdHJpY3RcIjtcbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwgeyB2YWx1ZTogdHJ1ZSB9KTtcbmNsYXNzIFN0eWxlTG9hZGluZ01vbml0b3Ige1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICB0aGlzLnVubG9hZGVkU3R5bGVzID0gW107XG4gICAgICAgIHRoaXMuZmluaXNoZWRMb2FkaW5nID0gZmFsc2U7XG4gICAgICAgIGNvbnN0IG9uU3R5bGVMb2FkRXJyb3IgPSAoZXZlbnQpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHNvdXJjZSA9IGV2ZW50LnRhcmdldC5kYXRhc2V0LnNvdXJjZTtcbiAgICAgICAgICAgIHRoaXMudW5sb2FkZWRTdHlsZXMucHVzaChzb3VyY2UpO1xuICAgICAgICB9O1xuICAgICAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignRE9NQ29udGVudExvYWRlZCcsICgpID0+IHtcbiAgICAgICAgICAgIGZvciAoY29uc3QgbGluayBvZiBkb2N1bWVudC5nZXRFbGVtZW50c0J5Q2xhc3NOYW1lKCdjb2RlLXVzZXItc3R5bGUnKSkge1xuICAgICAgICAgICAgICAgIGlmIChsaW5rLmRhdGFzZXQuc291cmNlKSB7XG4gICAgICAgICAgICAgICAgICAgIGxpbmsub25lcnJvciA9IG9uU3R5bGVMb2FkRXJyb3I7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICAgICAgd2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ2xvYWQnLCAoKSA9PiB7XG4gICAgICAgICAgICBpZiAoIXRoaXMudW5sb2FkZWRTdHlsZXMubGVuZ3RoKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdGhpcy5maW5pc2hlZExvYWRpbmcgPSB0cnVlO1xuICAgICAgICAgICAgaWYgKHRoaXMucG9zdGVyKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5wb3N0ZXIucG9zdE1lc3NhZ2UoJ3ByZXZpZXdTdHlsZUxvYWRFcnJvcicsIHsgdW5sb2FkZWRTdHlsZXM6IHRoaXMudW5sb2FkZWRTdHlsZXMgfSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgIH1cbiAgICBzZXRQb3N0ZXIocG9zdGVyKSB7XG4gICAgICAgIHRoaXMucG9zdGVyID0gcG9zdGVyO1xuICAgICAgICBpZiAodGhpcy5maW5pc2hlZExvYWRpbmcpIHtcbiAgICAgICAgICAgIHBvc3Rlci5wb3N0TWVzc2FnZSgncHJldmlld1N0eWxlTG9hZEVycm9yJywgeyB1bmxvYWRlZFN0eWxlczogdGhpcy51bmxvYWRlZFN0eWxlcyB9KTtcbiAgICAgICAgfVxuICAgIH1cbn1cbmV4cG9ydHMuU3R5bGVMb2FkaW5nTW9uaXRvciA9IFN0eWxlTG9hZGluZ01vbml0b3I7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuY29uc3QgY3NwXzEgPSByZXF1aXJlKFwiLi9jc3BcIik7XG5jb25zdCBsb2FkaW5nXzEgPSByZXF1aXJlKFwiLi9sb2FkaW5nXCIpO1xud2luZG93LmNzcEFsZXJ0ZXIgPSBuZXcgY3NwXzEuQ3NwQWxlcnRlcigpO1xud2luZG93LnN0eWxlTG9hZGluZ01vbml0b3IgPSBuZXcgbG9hZGluZ18xLlN0eWxlTG9hZGluZ01vbml0b3IoKTtcbiIsIlwidXNlIHN0cmljdFwiO1xuLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgXCJfX2VzTW9kdWxlXCIsIHsgdmFsdWU6IHRydWUgfSk7XG5sZXQgY2FjaGVkU2V0dGluZ3MgPSB1bmRlZmluZWQ7XG5mdW5jdGlvbiBnZXREYXRhKGtleSkge1xuICAgIGNvbnN0IGVsZW1lbnQgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgndnNjb2RlLW1hcmtkb3duLXByZXZpZXctZGF0YScpO1xuICAgIGlmIChlbGVtZW50KSB7XG4gICAgICAgIGNvbnN0IGRhdGEgPSBlbGVtZW50LmdldEF0dHJpYnV0ZShrZXkpO1xuICAgICAgICBpZiAoZGF0YSkge1xuICAgICAgICAgICAgcmV0dXJuIEpTT04ucGFyc2UoZGF0YSk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgdGhyb3cgbmV3IEVycm9yKGBDb3VsZCBub3QgbG9hZCBkYXRhIGZvciAke2tleX1gKTtcbn1cbmV4cG9ydHMuZ2V0RGF0YSA9IGdldERhdGE7XG5mdW5jdGlvbiBnZXRTZXR0aW5ncygpIHtcbiAgICBpZiAoY2FjaGVkU2V0dGluZ3MpIHtcbiAgICAgICAgcmV0dXJuIGNhY2hlZFNldHRpbmdzO1xuICAgIH1cbiAgICBjYWNoZWRTZXR0aW5ncyA9IGdldERhdGEoJ2RhdGEtc2V0dGluZ3MnKTtcbiAgICBpZiAoY2FjaGVkU2V0dGluZ3MpIHtcbiAgICAgICAgcmV0dXJuIGNhY2hlZFNldHRpbmdzO1xuICAgIH1cbiAgICB0aHJvdyBuZXcgRXJyb3IoJ0NvdWxkIG5vdCBsb2FkIHNldHRpbmdzJyk7XG59XG5leHBvcnRzLmdldFNldHRpbmdzID0gZ2V0U2V0dGluZ3M7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuZnVuY3Rpb24gZ2V0U3RyaW5ncygpIHtcbiAgICBjb25zdCBzdG9yZSA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd2c2NvZGUtbWFya2Rvd24tcHJldmlldy1kYXRhJyk7XG4gICAgaWYgKHN0b3JlKSB7XG4gICAgICAgIGNvbnN0IGRhdGEgPSBzdG9yZS5nZXRBdHRyaWJ1dGUoJ2RhdGEtc3RyaW5ncycpO1xuICAgICAgICBpZiAoZGF0YSkge1xuICAgICAgICAgICAgcmV0dXJuIEpTT04ucGFyc2UoZGF0YSk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgdGhyb3cgbmV3IEVycm9yKCdDb3VsZCBub3QgbG9hZCBzdHJpbmdzJyk7XG59XG5leHBvcnRzLmdldFN0cmluZ3MgPSBnZXRTdHJpbmdzO1xuIl0sInNvdXJjZVJvb3QiOiIifQ== +!function(e){var t={};function s(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,s),o.l=!0,o.exports}s.m=e,s.c=t,s.d=function(e,t,n){s.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:n})},s.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},s.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return s.d(t,"a",t),t},s.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},s.p="",s(s.s=5)}([function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});let n=void 0;function o(e){const t=document.getElementById("vscode-markdown-preview-data");if(t){const s=t.getAttribute(e);if(s)return JSON.parse(s)}throw new Error(`Could not load data for ${e}`)}t.getData=o,t.getSettings=function(){if(n)return n;if(n=o("data-settings"))return n;throw new Error("Could not load settings")}},,function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.StyleLoadingMonitor=class{constructor(){this.unloadedStyles=[],this.finishedLoading=!1;const e=e=>{const t=e.target.dataset.source;this.unloadedStyles.push(t)};window.addEventListener("DOMContentLoaded",()=>{for(const t of document.getElementsByClassName("code-user-style"))t.dataset.source&&(t.onerror=e)}),window.addEventListener("load",()=>{this.unloadedStyles.length&&(this.finishedLoading=!0,this.poster&&this.poster.postMessage("previewStyleLoadError",{unloadedStyles:this.unloadedStyles}))})}setPoster(e){this.poster=e,this.finishedLoading&&e.postMessage("previewStyleLoadError",{unloadedStyles:this.unloadedStyles})}}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getStrings=function(){const e=document.getElementById("vscode-markdown-preview-data");if(e){const t=e.getAttribute("data-strings");if(t)return JSON.parse(t)}throw new Error("Could not load strings")}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=s(0),o=s(3);t.CspAlerter=class{constructor(){this.didShow=!1,this.didHaveCspWarning=!1,document.addEventListener("securitypolicyviolation",()=>{this.onCspWarning()}),window.addEventListener("message",e=>{e&&e.data&&"vscode-did-block-svg"===e.data.name&&this.onCspWarning()})}setPoster(e){this.messaging=e,this.didHaveCspWarning&&this.showCspWarning()}onCspWarning(){this.didHaveCspWarning=!0,this.showCspWarning()}showCspWarning(){const e=o.getStrings(),t=n.getSettings();if(this.didShow||t.disableSecurityWarnings||!this.messaging)return;this.didShow=!0;const s=document.createElement("a");s.innerText=e.cspAlertMessageText,s.setAttribute("id","code-csp-warning"),s.setAttribute("title",e.cspAlertMessageTitle),s.setAttribute("role","button"),s.setAttribute("aria-label",e.cspAlertMessageLabel),s.onclick=(()=>{this.messaging.postMessage("showPreviewSecuritySelector",{source:t.source})}),document.body.appendChild(s)}}},function(e,t,s){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=s(4),o=s(2);window.cspAlerter=new n.CspAlerter,window.styleLoadingMonitor=new o.StyleLoadingMonitor}]); +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvc2V0dGluZ3MudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvbG9hZGluZy50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9zdHJpbmdzLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2NzcC50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9wcmUudHMiXSwibmFtZXMiOlsiaW5zdGFsbGVkTW9kdWxlcyIsIl9fd2VicGFja19yZXF1aXJlX18iLCJtb2R1bGVJZCIsImV4cG9ydHMiLCJtb2R1bGUiLCJpIiwibCIsIm1vZHVsZXMiLCJjYWxsIiwibSIsImMiLCJkIiwibmFtZSIsImdldHRlciIsIm8iLCJPYmplY3QiLCJkZWZpbmVQcm9wZXJ0eSIsImNvbmZpZ3VyYWJsZSIsImVudW1lcmFibGUiLCJnZXQiLCJyIiwidmFsdWUiLCJuIiwiX19lc01vZHVsZSIsIm9iamVjdCIsInByb3BlcnR5IiwicHJvdG90eXBlIiwiaGFzT3duUHJvcGVydHkiLCJwIiwicyIsImNhY2hlZFNldHRpbmdzIiwidW5kZWZpbmVkIiwiZ2V0RGF0YSIsImtleSIsImVsZW1lbnQiLCJkb2N1bWVudCIsImdldEVsZW1lbnRCeUlkIiwiZGF0YSIsImdldEF0dHJpYnV0ZSIsIkpTT04iLCJwYXJzZSIsIkVycm9yIiwiZ2V0U2V0dGluZ3MiLCJTdHlsZUxvYWRpbmdNb25pdG9yIiwiW29iamVjdCBPYmplY3RdIiwidGhpcyIsInVubG9hZGVkU3R5bGVzIiwiZmluaXNoZWRMb2FkaW5nIiwib25TdHlsZUxvYWRFcnJvciIsImV2ZW50Iiwic291cmNlIiwidGFyZ2V0IiwiZGF0YXNldCIsInB1c2giLCJ3aW5kb3ciLCJhZGRFdmVudExpc3RlbmVyIiwibGluayIsImdldEVsZW1lbnRzQnlDbGFzc05hbWUiLCJvbmVycm9yIiwibGVuZ3RoIiwicG9zdGVyIiwicG9zdE1lc3NhZ2UiLCJnZXRTdHJpbmdzIiwic3RvcmUiLCJzZXR0aW5nc18xIiwic3RyaW5nc18xIiwiQ3NwQWxlcnRlciIsImRpZFNob3ciLCJkaWRIYXZlQ3NwV2FybmluZyIsIm9uQ3NwV2FybmluZyIsIm1lc3NhZ2luZyIsInNob3dDc3BXYXJuaW5nIiwic3RyaW5ncyIsInNldHRpbmdzIiwiZGlzYWJsZVNlY3VyaXR5V2FybmluZ3MiLCJub3RpZmljYXRpb24iLCJjcmVhdGVFbGVtZW50IiwiaW5uZXJUZXh0IiwiY3NwQWxlcnRNZXNzYWdlVGV4dCIsInNldEF0dHJpYnV0ZSIsImNzcEFsZXJ0TWVzc2FnZVRpdGxlIiwiY3NwQWxlcnRNZXNzYWdlTGFiZWwiLCJvbmNsaWNrIiwiYm9keSIsImFwcGVuZENoaWxkIiwiY3NwXzEiLCJsb2FkaW5nXzEiLCJjc3BBbGVydGVyIiwic3R5bGVMb2FkaW5nTW9uaXRvciJdLCJtYXBwaW5ncyI6ImFBQ0EsSUFBQUEsS0FHQSxTQUFBQyxFQUFBQyxHQUdBLEdBQUFGLEVBQUFFLEdBQ0EsT0FBQUYsRUFBQUUsR0FBQUMsUUFHQSxJQUFBQyxFQUFBSixFQUFBRSxJQUNBRyxFQUFBSCxFQUNBSSxHQUFBLEVBQ0FILFlBVUEsT0FOQUksRUFBQUwsR0FBQU0sS0FBQUosRUFBQUQsUUFBQUMsSUFBQUQsUUFBQUYsR0FHQUcsRUFBQUUsR0FBQSxFQUdBRixFQUFBRCxRQUtBRixFQUFBUSxFQUFBRixFQUdBTixFQUFBUyxFQUFBVixFQUdBQyxFQUFBVSxFQUFBLFNBQUFSLEVBQUFTLEVBQUFDLEdBQ0FaLEVBQUFhLEVBQUFYLEVBQUFTLElBQ0FHLE9BQUFDLGVBQUFiLEVBQUFTLEdBQ0FLLGNBQUEsRUFDQUMsWUFBQSxFQUNBQyxJQUFBTixLQU1BWixFQUFBbUIsRUFBQSxTQUFBakIsR0FDQVksT0FBQUMsZUFBQWIsRUFBQSxjQUFpRGtCLE9BQUEsS0FJakRwQixFQUFBcUIsRUFBQSxTQUFBbEIsR0FDQSxJQUFBUyxFQUFBVCxLQUFBbUIsV0FDQSxXQUEyQixPQUFBbkIs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QUFBdkIsV0FFdkVmLFNBQUFnRCxLQUFBQyxZQUFBVCxtQ0M3Q0E1RCxPQUFBQyxlQUFBYixFQUFBLGNBQThDa0IsT0FBQSxJQUM5QyxNQUFBZ0UsRUFBQXBGLEVBQUEsR0FDQXFGLEVBQUFyRixFQUFBLEdBQ0FxRCxPQUFBaUMsV0FBQSxJQUFBRixFQUFBbkIsV0FDQVosT0FBQWtDLG9CQUFBLElBQUFGLEVBQUEzQyIsImZpbGUiOiJwcmUuanMiLCJzb3VyY2VzQ29udGVudCI6WyIgXHQvLyBUaGUgbW9kdWxlIGNhY2hlXG4gXHR2YXIgaW5zdGFsbGVkTW9kdWxlcyA9IHt9O1xuXG4gXHQvLyBUaGUgcmVxdWlyZSBmdW5jdGlvblxuIFx0ZnVuY3Rpb24gX193ZWJwYWNrX3JlcXVpcmVfXyhtb2R1bGVJZCkge1xuXG4gXHRcdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuIFx0XHRpZihpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSkge1xuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuIFx0XHR9XG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRpOiBtb2R1bGVJZCxcbiBcdFx0XHRsOiBmYWxzZSxcbiBcdFx0XHRleHBvcnRzOiB7fVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sID0gdHJ1ZTtcblxuIFx0XHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuIFx0XHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG4gXHR9XG5cblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXylcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubSA9IG1vZHVsZXM7XG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlIGNhY2hlXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmMgPSBpbnN0YWxsZWRNb2R1bGVzO1xuXG4gXHQvLyBkZWZpbmUgZ2V0dGVyIGZ1bmN0aW9uIGZvciBoYXJtb255IGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uZCA9IGZ1bmN0aW9uKGV4cG9ydHMsIG5hbWUsIGdldHRlcikge1xuIFx0XHRpZighX193ZWJwYWNrX3JlcXVpcmVfXy5vKGV4cG9ydHMsIG5hbWUpKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIG5hbWUsIHtcbiBcdFx0XHRcdGNvbmZpZ3VyYWJsZTogZmFsc2UsXG4gXHRcdFx0XHRlbnVtZXJhYmxlOiB0cnVlLFxuIFx0XHRcdFx0Z2V0OiBnZXR0ZXJcbiBcdFx0XHR9KTtcbiBcdFx0fVxuIFx0fTtcblxuIFx0Ly8gZGVmaW5lIF9fZXNNb2R1bGUgb24gZXhwb3J0c1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5yID0gZnVuY3Rpb24oZXhwb3J0cykge1xuIFx0XHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7IHZhbHVlOiB0cnVlIH0pO1xuIFx0fTtcblxuIFx0Ly8gZ2V0RGVmYXVsdEV4cG9ydCBmdW5jdGlvbiBmb3IgY29tcGF0aWJpbGl0eSB3aXRoIG5vbi1oYXJtb255IG1vZHVsZXNcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubiA9IGZ1bmN0aW9uKG1vZHVsZSkge1xuIFx0XHR2YXIgZ2V0dGVyID0gbW9kdWxlICYmIG1vZHVsZS5fX2VzTW9kdWxlID9cbiBcdFx0XHRmdW5jdGlvbiBnZXREZWZhdWx0KCkgeyByZXR1cm4gbW9kdWxlWydkZWZhdWx0J107IH0gOlxuIFx0XHRcdGZ1bmN0aW9uIGdldE1vZHVsZUV4cG9ydHMoKSB7IHJldHVybiBtb2R1bGU7IH07XG4gXHRcdF9fd2VicGFja19yZXF1aXJlX18uZChnZXR0ZXIsICdhJywgZ2V0dGVyKTtcbiBcdFx0cmV0dXJuIGdldHRlcjtcbiBcdH07XG5cbiBcdC8vIE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbFxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5vID0gZnVuY3Rpb24ob2JqZWN0LCBwcm9wZXJ0eSkgeyByZXR1cm4gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKG9iamVjdCwgcHJvcGVydHkpOyB9O1xuXG4gXHQvLyBfX3dlYnBhY2tfcHVibGljX3BhdGhfX1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5wID0gXCJcIjtcblxuXG4gXHQvLyBMb2FkIGVudHJ5IG1vZHVsZSBhbmQgcmV0dXJuIGV4cG9ydHNcbiBcdHJldHVybiBfX3dlYnBhY2tfcmVxdWlyZV9fKF9fd2VicGFja19yZXF1aXJlX18ucyA9IDUpO1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBcIl9fZXNNb2R1bGVcIiwgeyB2YWx1ZTogdHJ1ZSB9KTtcbmxldCBjYWNoZWRTZXR0aW5ncyA9IHVuZGVmaW5lZDtcbmZ1bmN0aW9uIGdldERhdGEoa2V5KSB7XG4gICAgY29uc3QgZWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd2c2NvZGUtbWFya2Rvd24tcHJldmlldy1kYXRhJyk7XG4gICAgaWYgKGVsZW1lbnQpIHtcbiAgICAgICAgY29uc3QgZGF0YSA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKGtleSk7XG4gICAgICAgIGlmIChkYXRhKSB7XG4gICAgICAgICAgICByZXR1cm4gSlNPTi5wYXJzZShkYXRhKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICB0aHJvdyBuZXcgRXJyb3IoYENvdWxkIG5vdCBsb2FkIGRhdGEgZm9yICR7a2V5fWApO1xufVxuZXhwb3J0cy5nZXREYXRhID0gZ2V0RGF0YTtcbmZ1bmN0aW9uIGdldFNldHRpbmdzKCkge1xuICAgIGlmIChjYWNoZWRTZXR0aW5ncykge1xuICAgICAgICByZXR1cm4gY2FjaGVkU2V0dGluZ3M7XG4gICAgfVxuICAgIGNhY2hlZFNldHRpbmdzID0gZ2V0RGF0YSgnZGF0YS1zZXR0aW5ncycpO1xuICAgIGlmIChjYWNoZWRTZXR0aW5ncykge1xuICAgICAgICByZXR1cm4gY2FjaGVkU2V0dGluZ3M7XG4gICAgfVxuICAgIHRocm93IG5ldyBFcnJvcignQ291bGQgbm90IGxvYWQgc2V0dGluZ3MnKTtcbn1cbmV4cG9ydHMuZ2V0U2V0dGluZ3MgPSBnZXRTZXR0aW5ncztcbiIsIlwidXNlIHN0cmljdFwiO1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuY2xhc3MgU3R5bGVMb2FkaW5nTW9uaXRvciB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMudW5sb2FkZWRTdHlsZXMgPSBbXTtcbiAgICAgICAgdGhpcy5maW5pc2hlZExvYWRpbmcgPSBmYWxzZTtcbiAgICAgICAgY29uc3Qgb25TdHlsZUxvYWRFcnJvciA9IChldmVudCkgPT4ge1xuICAgICAgICAgICAgY29uc3Qgc291cmNlID0gZXZlbnQudGFyZ2V0LmRhdGFzZXQuc291cmNlO1xuICAgICAgICAgICAgdGhpcy51bmxvYWRlZFN0eWxlcy5wdXNoKHNvdXJjZSk7XG4gICAgICAgIH07XG4gICAgICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdET01Db250ZW50TG9hZGVkJywgKCkgPT4ge1xuICAgICAgICAgICAgZm9yIChjb25zdCBsaW5rIG9mIGRvY3VtZW50LmdldEVsZW1lbnRzQnlDbGFzc05hbWUoJ2NvZGUtdXNlci1zdHlsZScpKSB7XG4gICAgICAgICAgICAgICAgaWYgKGxpbmsuZGF0YXNldC5zb3VyY2UpIHtcbiAgICAgICAgICAgICAgICAgICAgbGluay5vbmVycm9yID0gb25TdHlsZUxvYWRFcnJvcjtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgICAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbG9hZCcsICgpID0+IHtcbiAgICAgICAgICAgIGlmICghdGhpcy51bmxvYWRlZFN0eWxlcy5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICB0aGlzLmZpbmlzaGVkTG9hZGluZyA9IHRydWU7XG4gICAgICAgICAgICBpZiAodGhpcy5wb3N0ZXIpIHtcbiAgICAgICAgICAgICAgICB0aGlzLnBvc3Rlci5wb3N0TWVzc2FnZSgncHJldmlld1N0eWxlTG9hZEVycm9yJywgeyB1bmxvYWRlZFN0eWxlczogdGhpcy51bmxvYWRlZFN0eWxlcyB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgfVxuICAgIHNldFBvc3Rlcihwb3N0ZXIpIHtcbiAgICAgICAgdGhpcy5wb3N0ZXIgPSBwb3N0ZXI7XG4gICAgICAgIGlmICh0aGlzLmZpbmlzaGVkTG9hZGluZykge1xuICAgICAgICAgICAgcG9zdGVyLnBvc3RNZXNzYWdlKCdwcmV2aWV3U3R5bGVMb2FkRXJyb3InLCB7IHVubG9hZGVkU3R5bGVzOiB0aGlzLnVubG9hZGVkU3R5bGVzIH0pO1xuICAgICAgICB9XG4gICAgfVxufVxuZXhwb3J0cy5TdHlsZUxvYWRpbmdNb25pdG9yID0gU3R5bGVMb2FkaW5nTW9uaXRvcjtcbiIsIlwidXNlIHN0cmljdFwiO1xuLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5PYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgXCJfX2VzTW9kdWxlXCIsIHsgdmFsdWU6IHRydWUgfSk7XG5mdW5jdGlvbiBnZXRTdHJpbmdzKCkge1xuICAgIGNvbnN0IHN0b3JlID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3ZzY29kZS1tYXJrZG93bi1wcmV2aWV3LWRhdGEnKTtcbiAgICBpZiAoc3RvcmUpIHtcbiAgICAgICAgY29uc3QgZGF0YSA9IHN0b3JlLmdldEF0dHJpYnV0ZSgnZGF0YS1zdHJpbmdzJyk7XG4gICAgICAgIGlmIChkYXRhKSB7XG4gICAgICAgICAgICByZXR1cm4gSlNPTi5wYXJzZShkYXRhKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICB0aHJvdyBuZXcgRXJyb3IoJ0NvdWxkIG5vdCBsb2FkIHN0cmluZ3MnKTtcbn1cbmV4cG9ydHMuZ2V0U3RyaW5ncyA9IGdldFN0cmluZ3M7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuY29uc3Qgc2V0dGluZ3NfMSA9IHJlcXVpcmUoXCIuL3NldHRpbmdzXCIpO1xuY29uc3Qgc3RyaW5nc18xID0gcmVxdWlyZShcIi4vc3RyaW5nc1wiKTtcbi8qKlxuICogU2hvd3MgYW4gYWxlcnQgd2hlbiB0aGVyZSBpcyBhIGNvbnRlbnQgc2VjdXJpdHkgcG9saWN5IHZpb2xhdGlvbi5cbiAqL1xuY2xhc3MgQ3NwQWxlcnRlciB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMuZGlkU2hvdyA9IGZhbHNlO1xuICAgICAgICB0aGlzLmRpZEhhdmVDc3BXYXJuaW5nID0gZmFsc2U7XG4gICAgICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ3NlY3VyaXR5cG9saWN5dmlvbGF0aW9uJywgKCkgPT4ge1xuICAgICAgICAgICAgdGhpcy5vbkNzcFdhcm5pbmcoKTtcbiAgICAgICAgfSk7XG4gICAgICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtZXNzYWdlJywgKGV2ZW50KSA9PiB7XG4gICAgICAgICAgICBpZiAoZXZlbnQgJiYgZXZlbnQuZGF0YSAmJiBldmVudC5kYXRhLm5hbWUgPT09ICd2c2NvZGUtZGlkLWJsb2NrLXN2ZycpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9uQ3NwV2FybmluZygpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICB9XG4gICAgc2V0UG9zdGVyKHBvc3Rlcikge1xuICAgICAgICB0aGlzLm1lc3NhZ2luZyA9IHBvc3RlcjtcbiAgICAgICAgaWYgKHRoaXMuZGlkSGF2ZUNzcFdhcm5pbmcpIHtcbiAgICAgICAgICAgIHRoaXMuc2hvd0NzcFdhcm5pbmcoKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICBvbkNzcFdhcm5pbmcoKSB7XG4gICAgICAgIHRoaXMuZGlkSGF2ZUNzcFdhcm5pbmcgPSB0cnVlO1xuICAgICAgICB0aGlzLnNob3dDc3BXYXJuaW5nKCk7XG4gICAgfVxuICAgIHNob3dDc3BXYXJuaW5nKCkge1xuICAgICAgICBjb25zdCBzdHJpbmdzID0gc3RyaW5nc18xLmdldFN0cmluZ3MoKTtcbiAgICAgICAgY29uc3Qgc2V0dGluZ3MgPSBzZXR0aW5nc18xLmdldFNldHRpbmdzKCk7XG4gICAgICAgIGlmICh0aGlzLmRpZFNob3cgfHwgc2V0dGluZ3MuZGlzYWJsZVNlY3VyaXR5V2FybmluZ3MgfHwgIXRoaXMubWVzc2FnaW5nKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5kaWRTaG93ID0gdHJ1ZTtcbiAgICAgICAgY29uc3Qgbm90aWZpY2F0aW9uID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnYScpO1xuICAgICAgICBub3RpZmljYXRpb24uaW5uZXJUZXh0ID0gc3RyaW5ncy5jc3BBbGVydE1lc3NhZ2VUZXh0O1xuICAgICAgICBub3RpZmljYXRpb24uc2V0QXR0cmlidXRlKCdpZCcsICdjb2RlLWNzcC13YXJuaW5nJyk7XG4gICAgICAgIG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ3RpdGxlJywgc3RyaW5ncy5jc3BBbGVydE1lc3NhZ2VUaXRsZSk7XG4gICAgICAgIG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ3JvbGUnLCAnYnV0dG9uJyk7XG4gICAgICAgIG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ2FyaWEtbGFiZWwnLCBzdHJpbmdzLmNzcEFsZXJ0TWVzc2FnZUxhYmVsKTtcbiAgICAgICAgbm90aWZpY2F0aW9uLm9uY2xpY2sgPSAoKSA9PiB7XG4gICAgICAgICAgICB0aGlzLm1lc3NhZ2luZy5wb3N0TWVzc2FnZSgnc2hvd1ByZXZpZXdTZWN1cml0eVNlbGVjdG9yJywgeyBzb3VyY2U6IHNldHRpbmdzLnNvdXJjZSB9KTtcbiAgICAgICAgfTtcbiAgICAgICAgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChub3RpZmljYXRpb24pO1xuICAgIH1cbn1cbmV4cG9ydHMuQ3NwQWxlcnRlciA9IENzcEFsZXJ0ZXI7XG4iLCJcInVzZSBzdHJpY3RcIjtcbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFwiX19lc01vZHVsZVwiLCB7IHZhbHVlOiB0cnVlIH0pO1xuY29uc3QgY3NwXzEgPSByZXF1aXJlKFwiLi9jc3BcIik7XG5jb25zdCBsb2FkaW5nXzEgPSByZXF1aXJlKFwiLi9sb2FkaW5nXCIpO1xud2luZG93LmNzcEFsZXJ0ZXIgPSBuZXcgY3NwXzEuQ3NwQWxlcnRlcigpO1xud2luZG93LnN0eWxlTG9hZGluZ01vbml0b3IgPSBuZXcgbG9hZGluZ18xLlN0eWxlTG9hZGluZ01vbml0b3IoKTtcbiJdLCJzb3VyY2VSb290IjoiIn0= diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 84b2c90542..78bfc5a419 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -1,350 +1,350 @@ { - "name": "markdown-language-features", - "displayName": "%displayName%", - "description": "%description%", - "version": "1.0.0", - "icon": "icon.png", - "publisher": "vscode", - "enableProposedApi": true, - "license": "MIT", - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", - "engines": { - "vscode": "^1.20.0" - }, - "main": "./out/extension", - "categories": [ - "Programming Languages" - ], - "activationEvents": [ - "onLanguage:markdown", - "onCommand:markdown.preview.toggleLock", - "onCommand:markdown.preview.refresh", - "onCommand:markdown.showPreview", - "onCommand:markdown.showPreviewToSide", - "onCommand:markdown.showLockedPreviewToSide", - "onCommand:markdown.showSource", - "onCommand:markdown.showPreviewSecuritySelector", - "onCommand:markdown.api.render", + "name": "markdown-language-features", + "displayName": "%displayName%", + "description": "%description%", + "version": "1.0.0", + "icon": "icon.png", + "publisher": "vscode", + "enableProposedApi": true, + "license": "MIT", + "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "engines": { + "vscode": "^1.20.0" + }, + "main": "./out/extension", + "categories": [ + "Programming Languages" + ], + "activationEvents": [ + "onLanguage:markdown", + "onCommand:markdown.preview.toggleLock", + "onCommand:markdown.preview.refresh", + "onCommand:markdown.showPreview", + "onCommand:markdown.showPreviewToSide", + "onCommand:markdown.showLockedPreviewToSide", + "onCommand:markdown.showSource", + "onCommand:markdown.showPreviewSecuritySelector", + "onCommand:markdown.api.render", "onCommand:notebook.showPreview", - "onWebviewPanel:markdown.preview" - ], - "contributes": { - "commands": [ - { - "command": "markdown.showPreview", - "title": "%markdown.preview.title%", - "category": "Markdown", - "icon": { - "light": "./media/preview-light.svg", - "dark": "./media/preview-dark.svg" - } - }, - { - "command": "markdown.showPreviewToSide", - "title": "%markdown.previewSide.title%", - "category": "Markdown", - "icon": { - "light": "./media/preview-right-light.svg", - "dark": "./media/preview-right-dark.svg" - } - }, - { - "command": "markdown.showLockedPreviewToSide", - "title": "%markdown.showLockedPreviewToSide.title%", - "category": "Markdown", - "icon": { - "light": "./media/preview-right-light.svg", - "dark": "./media/preview-right-dark.svg" - } - }, - { - "command": "markdown.showSource", - "title": "%markdown.showSource.title%", - "category": "Markdown", - "icon": { - "light": "./media/view-source-light.svg", - "dark": "./media/view-source-dark.svg" - } - }, - { - "command": "markdown.showPreviewSecuritySelector", - "title": "%markdown.showPreviewSecuritySelector.title%", - "category": "Markdown" - }, - { - "command": "markdown.preview.refresh", - "title": "%markdown.preview.refresh.title%", - "category": "Markdown" - }, - { - "command": "markdown.preview.toggleLock", - "title": "%markdown.preview.toggleLock.title%", - "category": "Markdown" - }, + "onWebviewPanel:markdown.preview" + ], + "contributes": { + "commands": [ + { + "command": "markdown.showPreview", + "title": "%markdown.preview.title%", + "category": "Markdown", + "icon": { + "light": "./media/preview-light.svg", + "dark": "./media/preview-dark.svg" + } + }, + { + "command": "markdown.showPreviewToSide", + "title": "%markdown.previewSide.title%", + "category": "Markdown", + "icon": { + "light": "./media/preview-right-light.svg", + "dark": "./media/preview-right-dark.svg" + } + }, + { + "command": "markdown.showLockedPreviewToSide", + "title": "%markdown.showLockedPreviewToSide.title%", + "category": "Markdown", + "icon": { + "light": "./media/preview-right-light.svg", + "dark": "./media/preview-right-dark.svg" + } + }, + { + "command": "markdown.showSource", + "title": "%markdown.showSource.title%", + "category": "Markdown", + "icon": { + "light": "./media/view-source-light.svg", + "dark": "./media/view-source-dark.svg" + } + }, + { + "command": "markdown.showPreviewSecuritySelector", + "title": "%markdown.showPreviewSecuritySelector.title%", + "category": "Markdown" + }, + { + "command": "markdown.preview.refresh", + "title": "%markdown.preview.refresh.title%", + "category": "Markdown" + }, + { + "command": "markdown.preview.toggleLock", + "title": "%markdown.preview.toggleLock.title%", + "category": "Markdown" + }, { "command": "notebook.showPreview", "title": "notebook.showPreview", "category": "Notebook" } - ], - "menus": { - "editor/title": [ - { - "command": "markdown.showPreviewToSide", - "when": "editorLangId == markdown", - "alt": "markdown.showPreview", - "group": "navigation" - }, - { - "command": "markdown.showSource", - "when": "markdownPreviewFocus", - "group": "navigation" - }, - { - "command": "markdown.preview.refresh", - "when": "markdownPreviewFocus", - "group": "1_markdown" - }, - { - "command": "markdown.preview.toggleLock", - "when": "markdownPreviewFocus", - "group": "1_markdown" - }, - { - "command": "markdown.showPreviewSecuritySelector", - "when": "markdownPreviewFocus", - "group": "1_markdown" - } - ], - "explorer/context": [ - { - "command": "markdown.showPreview", - "when": "resourceLangId == markdown", - "group": "navigation" - } - ], - "editor/title/context": [ - { - "command": "markdown.showPreview", - "when": "resourceLangId == markdown", - "group": "navigation" - } - ], - "commandPalette": [ - { - "command": "markdown.showPreview", - "when": "editorLangId == markdown", - "group": "navigation" - }, - { - "command": "markdown.showPreviewToSide", - "when": "editorLangId == markdown", - "group": "navigation" - }, - { - "command": "markdown.showLockedPreviewToSide", - "when": "editorLangId == markdown", - "group": "navigation" - }, - { - "command": "markdown.showSource", - "when": "markdownPreviewFocus", - "group": "navigation" - }, - { - "command": "markdown.showPreviewSecuritySelector", - "when": "editorLangId == markdown" - }, - { - "command": "markdown.showPreviewSecuritySelector", - "when": "markdownPreviewFocus" - }, - { - "command": "markdown.preview.toggleLock", - "when": "markdownPreviewFocus" - }, - { - "command": "markdown.preview.refresh", - "when": "editorLangId == markdown" - }, - { - "command": "markdown.preview.refresh", - "when": "markdownPreviewFocus" - }, + ], + "menus": { + "editor/title": [ + { + "command": "markdown.showPreviewToSide", + "when": "editorLangId == markdown", + "alt": "markdown.showPreview", + "group": "navigation" + }, + { + "command": "markdown.showSource", + "when": "markdownPreviewFocus", + "group": "navigation" + }, + { + "command": "markdown.preview.refresh", + "when": "markdownPreviewFocus", + "group": "1_markdown" + }, + { + "command": "markdown.preview.toggleLock", + "when": "markdownPreviewFocus", + "group": "1_markdown" + }, + { + "command": "markdown.showPreviewSecuritySelector", + "when": "markdownPreviewFocus", + "group": "1_markdown" + } + ], + "explorer/context": [ + { + "command": "markdown.showPreview", + "when": "resourceLangId == markdown", + "group": "navigation" + } + ], + "editor/title/context": [ + { + "command": "markdown.showPreview", + "when": "resourceLangId == markdown", + "group": "navigation" + } + ], + "commandPalette": [ + { + "command": "markdown.showPreview", + "when": "editorLangId == markdown", + "group": "navigation" + }, + { + "command": "markdown.showPreviewToSide", + "when": "editorLangId == markdown", + "group": "navigation" + }, + { + "command": "markdown.showLockedPreviewToSide", + "when": "editorLangId == markdown", + "group": "navigation" + }, + { + "command": "markdown.showSource", + "when": "markdownPreviewFocus", + "group": "navigation" + }, + { + "command": "markdown.showPreviewSecuritySelector", + "when": "editorLangId == markdown" + }, + { + "command": "markdown.showPreviewSecuritySelector", + "when": "markdownPreviewFocus" + }, + { + "command": "markdown.preview.toggleLock", + "when": "markdownPreviewFocus" + }, + { + "command": "markdown.preview.refresh", + "when": "editorLangId == markdown" + }, + { + "command": "markdown.preview.refresh", + "when": "markdownPreviewFocus" + }, { "command": "notebook.showPreview", "when": "false" } - ] - }, - "keybindings": [ - { - "command": "markdown.showPreview", - "key": "shift+ctrl+v", - "mac": "shift+cmd+v", - "when": "editorLangId == markdown" - }, - { - "command": "markdown.showPreviewToSide", - "key": "ctrl+k v", - "mac": "cmd+k v", - "when": "editorLangId == markdown" - } - ], - "configuration": { - "type": "object", - "title": "Markdown", - "order": 20, - "properties": { - "markdown.styles": { - "type": "array", - "items": { - "type": "string" - }, - "default": [], - "description": "%markdown.styles.dec%", - "scope": "resource" - }, - "markdown.preview.breaks": { - "type": "boolean", - "default": false, - "description": "%markdown.preview.breaks.desc%", - "scope": "resource" - }, - "markdown.preview.linkify": { - "type": "boolean", - "default": true, - "description": "%markdown.preview.linkify%", - "scope": "resource" - }, - "markdown.preview.fontFamily": { - "type": "string", - "default": "-apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', 'Ubuntu', 'Droid Sans', sans-serif", - "description": "%markdown.preview.fontFamily.desc%", - "scope": "resource" - }, - "markdown.preview.fontSize": { - "type": "number", - "default": 14, - "description": "%markdown.preview.fontSize.desc%", - "scope": "resource" - }, - "markdown.preview.lineHeight": { - "type": "number", - "default": 1.6, - "description": "%markdown.preview.lineHeight.desc%", - "scope": "resource" - }, - "markdown.preview.scrollPreviewWithEditor": { - "type": "boolean", - "default": true, - "description": "%markdown.preview.scrollPreviewWithEditor.desc%", - "scope": "resource" - }, - "markdown.preview.markEditorSelection": { - "type": "boolean", - "default": true, - "description": "%markdown.preview.markEditorSelection.desc%", - "scope": "resource" - }, - "markdown.preview.scrollEditorWithPreview": { - "type": "boolean", - "default": true, - "description": "%markdown.preview.scrollEditorWithPreview.desc%", - "scope": "resource" - }, - "markdown.preview.doubleClickToSwitchToEditor": { - "type": "boolean", - "default": true, - "description": "%markdown.preview.doubleClickToSwitchToEditor.desc%", - "scope": "resource" - }, - "markdown.preview.openMarkdownLinks": { - "type": "string", - "default": "inPreview", - "description": "%configuration.markdown.preview.openMarkdownLinks.description%", - "scope": "resource", - "enum": [ - "inPreview", - "inEditor" - ], - "enumDescriptions": [ - "%configuration.markdown.preview.openMarkdownLinks.inPreview%", - "%configuration.markdown.preview.openMarkdownLinks.inEditor%" - ] - }, - "markdown.links.openLocation": { - "type": "string", - "default": "currentGroup", - "description": "%configuration.markdown.links.openLocation.description%", - "scope": "resource", - "enum": [ - "currentGroup", - "beside" - ], - "enumDescriptions": [ - "%configuration.markdown.links.openLocation.currentGroup%", - "%configuration.markdown.links.openLocation.beside%" - ] - }, - "markdown.trace": { - "type": "string", - "enum": [ - "off", - "verbose" - ], - "default": "off", - "description": "%markdown.trace.desc%", - "scope": "window" - } - } - }, - "configurationDefaults": { - "[markdown]": { - "editor.wordWrap": "on", - "editor.quickSuggestions": false - } - }, - "jsonValidation": [ - { - "fileMatch": "package.json", - "url": "./schemas/package.schema.json" - } - ], - "markdown.previewStyles": [ - "./media/markdown.css", - "./media/highlight.css" - ], - "markdown.previewScripts": [ - "./media/index.js" - ] - }, - "scripts": { - "compile": "gulp compile-extension:markdown-language-features && npm run build-preview", - "watch": "npm run build-preview && gulp watch-extension:markdown-language-features", - "vscode:prepublish": "npm run build-ext && npm run build-preview", - "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:markdown-language-features ./tsconfig.json", - "build-preview": "webpack --mode development" - }, - "dependencies": { - "highlight.js": "9.15.8", - "markdown-it": "^9.1.0", - "markdown-it-front-matter": "^0.1.2", - "vscode-extension-telemetry": "0.1.1", - "vscode-nls": "^4.0.0" - }, - "devDependencies": { - "@types/highlight.js": "9.12.3", - "@types/lodash.throttle": "^4.1.3", - "@types/markdown-it": "0.0.2", - "@types/node": "^10.14.8", - "lodash.throttle": "^4.1.1", - "mocha-junit-reporter": "^1.17.0", - "mocha-multi-reporters": "^1.1.7", - "ts-loader": "^4.0.1", - "typescript": "^3.3.1", - "vscode": "^1.1.10", - "webpack": "^4.1.0", - "webpack-cli": "^2.0.10" - } + ] + }, + "keybindings": [ + { + "command": "markdown.showPreview", + "key": "shift+ctrl+v", + "mac": "shift+cmd+v", + "when": "editorLangId == markdown" + }, + { + "command": "markdown.showPreviewToSide", + "key": "ctrl+k v", + "mac": "cmd+k v", + "when": "editorLangId == markdown" + } + ], + "configuration": { + "type": "object", + "title": "Markdown", + "order": 20, + "properties": { + "markdown.styles": { + "type": "array", + "items": { + "type": "string" + }, + "default": [], + "description": "%markdown.styles.dec%", + "scope": "resource" + }, + "markdown.preview.breaks": { + "type": "boolean", + "default": false, + "description": "%markdown.preview.breaks.desc%", + "scope": "resource" + }, + "markdown.preview.linkify": { + "type": "boolean", + "default": true, + "description": "%markdown.preview.linkify%", + "scope": "resource" + }, + "markdown.preview.fontFamily": { + "type": "string", + "default": "-apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', 'Ubuntu', 'Droid Sans', sans-serif", + "description": "%markdown.preview.fontFamily.desc%", + "scope": "resource" + }, + "markdown.preview.fontSize": { + "type": "number", + "default": 14, + "description": "%markdown.preview.fontSize.desc%", + "scope": "resource" + }, + "markdown.preview.lineHeight": { + "type": "number", + "default": 1.6, + "description": "%markdown.preview.lineHeight.desc%", + "scope": "resource" + }, + "markdown.preview.scrollPreviewWithEditor": { + "type": "boolean", + "default": true, + "description": "%markdown.preview.scrollPreviewWithEditor.desc%", + "scope": "resource" + }, + "markdown.preview.markEditorSelection": { + "type": "boolean", + "default": true, + "description": "%markdown.preview.markEditorSelection.desc%", + "scope": "resource" + }, + "markdown.preview.scrollEditorWithPreview": { + "type": "boolean", + "default": true, + "description": "%markdown.preview.scrollEditorWithPreview.desc%", + "scope": "resource" + }, + "markdown.preview.doubleClickToSwitchToEditor": { + "type": "boolean", + "default": true, + "description": "%markdown.preview.doubleClickToSwitchToEditor.desc%", + "scope": "resource" + }, + "markdown.preview.openMarkdownLinks": { + "type": "string", + "default": "inPreview", + "description": "%configuration.markdown.preview.openMarkdownLinks.description%", + "scope": "resource", + "enum": [ + "inPreview", + "inEditor" + ], + "enumDescriptions": [ + "%configuration.markdown.preview.openMarkdownLinks.inPreview%", + "%configuration.markdown.preview.openMarkdownLinks.inEditor%" + ] + }, + "markdown.links.openLocation": { + "type": "string", + "default": "currentGroup", + "description": "%configuration.markdown.links.openLocation.description%", + "scope": "resource", + "enum": [ + "currentGroup", + "beside" + ], + "enumDescriptions": [ + "%configuration.markdown.links.openLocation.currentGroup%", + "%configuration.markdown.links.openLocation.beside%" + ] + }, + "markdown.trace": { + "type": "string", + "enum": [ + "off", + "verbose" + ], + "default": "off", + "description": "%markdown.trace.desc%", + "scope": "window" + } + } + }, + "configurationDefaults": { + "[markdown]": { + "editor.wordWrap": "on", + "editor.quickSuggestions": false + } + }, + "jsonValidation": [ + { + "fileMatch": "package.json", + "url": "./schemas/package.schema.json" + } + ], + "markdown.previewStyles": [ + "./media/markdown.css", + "./media/highlight.css" + ], + "markdown.previewScripts": [ + "./media/index.js" + ] + }, + "scripts": { + "compile": "gulp compile-extension:markdown-language-features && npm run build-preview", + "watch": "npm run build-preview && gulp watch-extension:markdown-language-features", + "vscode:prepublish": "npm run build-ext && npm run build-preview", + "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:markdown-language-features ./tsconfig.json", + "build-preview": "webpack --mode production" + }, + "dependencies": { + "highlight.js": "9.15.10", + "markdown-it": "^10.0.0", + "markdown-it-front-matter": "^0.1.2", + "vscode-extension-telemetry": "0.1.1", + "vscode-nls": "^4.0.0" + }, + "devDependencies": { + "@types/highlight.js": "9.12.3", + "@types/lodash.throttle": "^4.1.3", + "@types/markdown-it": "0.0.2", + "@types/node": "^10.14.8", + "lodash.throttle": "^4.1.1", + "mocha-junit-reporter": "^1.17.0", + "mocha-multi-reporters": "^1.1.7", + "ts-loader": "^4.0.1", + "typescript": "^3.3.1", + "vscode": "^1.1.10", + "webpack": "^4.1.0", + "webpack-cli": "^2.0.10" + } } diff --git a/extensions/markdown-language-features/preview-src/index.ts b/extensions/markdown-language-features/preview-src/index.ts index a38a1c363d..ea34ba3b32 100644 --- a/extensions/markdown-language-features/preview-src/index.ts +++ b/extensions/markdown-language-features/preview-src/index.ts @@ -19,7 +19,7 @@ const settings = getSettings(); const vscode = acquireVsCodeApi(); // Set VS Code state -let state = getData<{ line: number, fragment: string }>('data-state'); +let state = getData<{ line: number; fragment: string; }>('data-state'); vscode.setState(state); const messaging = createPosterForVsCode(vscode); @@ -67,7 +67,7 @@ const onUpdateView = (() => { })(); let updateImageSizes = throttle(() => { - const imageInfo: { id: string, height: number, width: number }[] = []; + const imageInfo: { id: string, height: number, width: number; }[] = []; let images = document.getElementsByTagName('img'); if (images) { let i; @@ -129,6 +129,8 @@ document.addEventListener('dblclick', event => { } }); +const passThroughLinkSchemes = ['http:', 'https:', 'mailto:', 'vscode:', 'vscode-insiders']; + document.addEventListener('click', event => { if (!event) { return; @@ -138,36 +140,40 @@ document.addEventListener('click', event => { while (node) { if (node.tagName && node.tagName === 'A' && node.href) { if (node.getAttribute('href').startsWith('#')) { - break; + return; } - if (node.href.startsWith('file://') || node.href.startsWith('vscode-resource:') || node.href.startsWith(settings.webviewResourceRoot)) { - const [path, fragment] = node.href.replace(/^(file:\/\/|vscode-resource:)/i, '').replace(new RegExp(`^${escapeRegExp(settings.webviewResourceRoot)}`)).split('#'); - messaging.postMessage('clickLink', { path, fragment }); + + // Pass through known schemes + if (passThroughLinkSchemes.some(scheme => node.href.startsWith(scheme))) { + return; + } + + const hrefText = node.getAttribute('data-href') || node.getAttribute('href'); + + // If original link doesn't look like a url, delegate back to VS Code to resolve + if (!/^[a-z\-]+:/i.test(hrefText)) { + messaging.postMessage('openLink', { href: hrefText }); event.preventDefault(); event.stopPropagation(); - break; + return; } - break; + + return; } node = node.parentNode; } }, true); -if (settings.scrollEditorWithPreview) { - window.addEventListener('scroll', throttle(() => { - if (scrollDisabled) { - scrollDisabled = false; - } else { - const line = getEditorLineNumberForPageOffset(window.scrollY); - if (typeof line === 'number' && !isNaN(line)) { - messaging.postMessage('revealLine', { line }); - state.line = line; - vscode.setState(state); - } +window.addEventListener('scroll', throttle(() => { + if (scrollDisabled) { + scrollDisabled = false; + } else { + const line = getEditorLineNumberForPageOffset(window.scrollY); + if (typeof line === 'number' && !isNaN(line)) { + messaging.postMessage('revealLine', { line }); + state.line = line; + vscode.setState(state); } - }, 50)); -} + } +}, 50)); -function escapeRegExp(text: string) { - return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); -} diff --git a/extensions/markdown-language-features/src/extension.ts b/extensions/markdown-language-features/src/extension.ts index 9566ccc3bf..6efb6b5a66 100644 --- a/extensions/markdown-language-features/src/extension.ts +++ b/extensions/markdown-language-features/src/extension.ts @@ -56,7 +56,7 @@ function registerMarkdownLanguageFeatures( return vscode.Disposable.from( vscode.languages.setLanguageConfiguration('markdown', { - wordPattern: new RegExp('(\\p{Alphabetic}|\\p{Number})+', 'ug'), + wordPattern: new RegExp('(\\p{Alphabetic}|\\p{Number}|\\p{Nonspacing_Mark})+', 'ug'), }), vscode.languages.registerDocumentSymbolProvider(selector, symbolProvider), vscode.languages.registerDocumentLinkProvider(selector, new LinkProvider()), diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts index 9b9719fe16..2624bcdadb 100644 --- a/extensions/markdown-language-features/src/features/preview.ts +++ b/extensions/markdown-language-features/src/features/preview.ts @@ -25,7 +25,7 @@ interface WebviewMessage { interface CacheImageSizesMessage extends WebviewMessage { readonly type: 'cacheImageSizes'; - readonly body: { id: string, width: number, height: number }[]; + readonly body: { id: string, width: number, height: number; }[]; } interface RevealLineMessage extends WebviewMessage { @@ -43,10 +43,9 @@ interface DidClickMessage extends WebviewMessage { } interface ClickLinkMessage extends WebviewMessage { - readonly type: 'clickLink'; + readonly type: 'openLink'; readonly body: { - readonly path: string; - readonly fragment?: string; + readonly href: string; }; } @@ -88,7 +87,7 @@ export class MarkdownPreview extends Disposable { private forceUpdate = false; private isScrolling = false; private _disposed: boolean = false; - private imageInfo: { id: string, width: number, height: number }[] = []; + private imageInfo: { id: string, width: number, height: number; }[] = []; private scrollToFragment: string | undefined; public static async revive( @@ -202,8 +201,8 @@ export class MarkdownPreview extends Disposable { this.onDidClickPreview(e.body.line); break; - case 'clickLink': - this.onDidClickPreviewLink(e.body.path, e.body.fragment); + case 'openLink': + this.onDidClickPreviewLink(e.body.href); break; case 'showPreviewSecuritySelector': @@ -284,15 +283,17 @@ export class MarkdownPreview extends Disposable { super.dispose(); } - public update(resource: vscode.Uri) { - const editor = vscode.window.activeTextEditor; + public update(resource: vscode.Uri, isRefresh = true) { // Reposition scroll preview, position scroll to the top if active text editor // doesn't corresponds with preview + const editor = vscode.window.activeTextEditor; if (editor) { - if (editor.document.uri.fsPath === resource.fsPath) { - this.line = getVisibleLine(editor); - } else { - this.line = 0; + if (!isRefresh || this._previewConfigurations.loadAndCacheConfiguration(this._resource).scrollEditorWithPreview) { + if (editor.document.uri.fsPath === resource.fsPath) { + this.line = getVisibleLine(editor); + } else { + this.line = 0; + } } } @@ -320,7 +321,7 @@ export class MarkdownPreview extends Disposable { public refresh() { this.forceUpdate = true; - this.update(this._resource); + this.update(this._resource, true); } public updateConfiguration() { @@ -484,6 +485,12 @@ export class MarkdownPreview extends Disposable { private onDidScrollPreview(line: number) { this.line = line; + + const config = this._previewConfigurations.loadAndCacheConfiguration(this._resource); + if (!config.scrollEditorWithPreview) { + return; + } + for (const editor of vscode.window.visibleTextEditors) { if (!this.isPreviewOf(editor.document.uri)) { continue; @@ -528,12 +535,19 @@ export class MarkdownPreview extends Disposable { this.editor.webview.html = html; } - private async onDidClickPreviewLink(path: string, fragment: string | undefined) { - this.scrollToFragment = undefined; + private async onDidClickPreviewLink(href: string) { + let [hrefPath, fragment] = decodeURIComponent(href).split('#'); + + // We perviously already resolve absolute paths. + // Now make sure we handle relative file paths + if (hrefPath[0] !== '/') { + hrefPath = path.join(path.dirname(this.resource.path), hrefPath); + } + const config = vscode.workspace.getConfiguration('markdown', this.resource); const openLinks = config.get('preview.openMarkdownLinks', 'inPreview'); if (openLinks === 'inPreview') { - const markdownLink = await resolveLinkToMarkdownFile(path); + const markdownLink = await resolveLinkToMarkdownFile(hrefPath); if (markdownLink) { if (fragment) { this.scrollToFragment = fragment; @@ -543,10 +557,10 @@ export class MarkdownPreview extends Disposable { } } - vscode.commands.executeCommand('_markdown.openDocumentLink', { path, fragment, fromResource: this.resource }); + vscode.commands.executeCommand('_markdown.openDocumentLink', { path: hrefPath, fragment, fromResource: this.resource }); } - private async onCacheImageSizes(imageInfo: { id: string, width: number, height: number }[]) { + private async onCacheImageSizes(imageInfo: { id: string, width: number, height: number; }[]) { this.imageInfo = imageInfo; } } diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts index 6cc40396f6..74d7634d8a 100644 --- a/extensions/markdown-language-features/src/markdownEngine.ts +++ b/extensions/markdown-language-features/src/markdownEngine.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as crypto from 'crypto'; -import { MarkdownIt, Token } from 'markdown-it'; import * as path from 'path'; +import { MarkdownIt, Token } from 'markdown-it'; import * as vscode from 'vscode'; import { MarkdownContributionProvider as MarkdownContributionProvider } from './markdownExtensions'; import { Slugifier } from './slugify'; import { SkinnyTextDocument } from './tableOfContentsProvider'; -import { getUriForLinkWithKnownExternalScheme } from './util/links'; +import { Schemes, isOfScheme } from './util/links'; const UNICODE_NEWLINE_REGEX = /\u2028|\u2029/g; @@ -105,10 +105,10 @@ export class MarkdownEngine { this.addImageStabilizer(md); this.addFencedRenderer(md); - this.addLinkNormalizer(md); this.addLinkValidator(md); this.addNamedHeaders(md); + this.addLinkRenderer(md); return md; }); } @@ -150,6 +150,7 @@ export class MarkdownEngine { public async render(input: SkinnyTextDocument | string): Promise { const config = this.getConfig(typeof input === 'string' ? undefined : input.uri); const engine = await this.getEngine(config); + const tokens = typeof input === 'string' ? this.tokenizeString(input, engine) : this.tokenizeDocument(input, config, engine); @@ -233,36 +234,28 @@ export class MarkdownEngine { const normalizeLink = md.normalizeLink; md.normalizeLink = (link: string) => { try { - const externalSchemeUri = getUriForLinkWithKnownExternalScheme(link); - if (externalSchemeUri) { - // set true to skip encoding - return normalizeLink(externalSchemeUri.toString(true)); - } + // If original link doesn't look like a url with a scheme, assume it must be a link to a file in workspace + if (!/^[a-z\-]+:/i.test(link)) { + // Use a fake scheme for parsing + let uri = vscode.Uri.parse('markdown-link:' + link); - // Assume it must be an relative or absolute file path - // Use a fake scheme to avoid parse warnings - let uri = vscode.Uri.parse(`vscode-resource:${link}`); - - if (uri.path) { - // Assume it must be a file - const fragment = uri.fragment; + // Relative paths should be resolved correctly inside the preview but we need to + // handle absolute paths specially (for images) to resolve them relative to the workspace root if (uri.path[0] === '/') { const root = vscode.workspace.getWorkspaceFolder(this.currentDocument!); if (root) { - uri = vscode.Uri.file(path.join(root.uri.fsPath, uri.path)); + uri = uri.with({ + path: path.join(root.uri.fsPath, uri.path), + }); } - } else { - uri = vscode.Uri.file(path.join(path.dirname(this.currentDocument!.path), uri.path)); } - if (fragment) { + if (uri.fragment) { uri = uri.with({ - fragment: this.slugifier.fromHeading(fragment).value + fragment: this.slugifier.fromHeading(uri.fragment).value }); } - return normalizeLink(uri.with({ scheme: 'vscode-resource' }).toString(true)); - } else if (!uri.path && uri.fragment) { - return `#${this.slugifier.fromHeading(uri.fragment).value}`; + return normalizeLink(uri.toString(true).replace(/^markdown-link:/, '')); } } catch (e) { // noop @@ -275,7 +268,7 @@ export class MarkdownEngine { const validateLink = md.validateLink; md.validateLink = (link: string) => { // support file:// links - return validateLink(link) || link.startsWith('file:') || /^data:image\/.*?;/.test(link); + return validateLink(link) || isOfScheme(Schemes.file, link) || /^data:image\/.*?;/.test(link); }; } @@ -303,6 +296,22 @@ export class MarkdownEngine { } }; } + + private addLinkRenderer(md: any): void { + const old_render = md.renderer.rules.link_open || ((tokens: any, idx: number, options: any, _env: any, self: any) => { + return self.renderToken(tokens, idx, options); + }); + + md.renderer.rules.link_open = (tokens: any, idx: number, options: any, env: any, self: any) => { + const token = tokens[idx]; + const hrefIndex = token.attrIndex('href'); + if (hrefIndex >= 0) { + const href = token.attrs[hrefIndex][1]; + token.attrPush(['data-href', href]); + } + return old_render(tokens, idx, options, env, self); + }; + } } async function getMarkdownOptions(md: () => MarkdownIt) { @@ -310,16 +319,7 @@ async function getMarkdownOptions(md: () => MarkdownIt) { return { html: true, highlight: (str: string, lang?: string) => { - // Workaround for highlight not supporting tsx: https://github.com/isagalaev/highlight.js/issues/1155 - if (lang && ['tsx', 'typescriptreact'].includes(lang.toLocaleLowerCase())) { - lang = 'jsx'; - } - if (lang && lang.toLocaleLowerCase() === 'json5') { - lang = 'json'; - } - if (lang && ['c#', 'csharp'].includes(lang.toLocaleLowerCase())) { - lang = 'cs'; - } + lang = normalizeHighlightLang(lang); if (lang && hljs.getLanguage(lang)) { try { return `
${hljs.highlight(lang, str, true).value}
`; @@ -330,3 +330,24 @@ async function getMarkdownOptions(md: () => MarkdownIt) { } }; } + +function normalizeHighlightLang(lang: string | undefined) { + switch (lang && lang.toLowerCase()) { + case 'tsx': + case 'typescriptreact': + // Workaround for highlight not supporting tsx: https://github.com/isagalaev/highlight.js/issues/1155 + return 'jsx'; + + case 'json5': + case 'jsonc': + return 'json'; + + case 'c#': + case 'csharp': + return 'cs'; + + default: + return lang; + } +} + diff --git a/extensions/markdown-language-features/src/util/links.ts b/extensions/markdown-language-features/src/util/links.ts index d3d1198a38..b4b0e35331 100644 --- a/extensions/markdown-language-features/src/util/links.ts +++ b/extensions/markdown-language-features/src/util/links.ts @@ -5,14 +5,30 @@ import * as vscode from 'vscode'; -const knownSchemes = ['http:', 'https:', 'file:', 'mailto:', 'data:', `${vscode.env.uriScheme}:`, 'vscode:', 'vscode-insiders:', 'vscode-resource:']; +export const Schemes = { + http: 'http:', + https: 'https:', + file: 'file:', + mailto: 'mailto:', + data: 'data:', + vscode: 'vscode:', + 'vscode-insiders': 'vscode-insiders:', + 'vscode-resource': 'vscode-resource', +}; -export function getUriForLinkWithKnownExternalScheme( - link: string, -): vscode.Uri | undefined { - if (knownSchemes.some(knownScheme => link.toLowerCase().startsWith(knownScheme))) { +const knownSchemes = [ + ...Object.values(Schemes), + `${vscode.env.uriScheme}:` +]; + +export function getUriForLinkWithKnownExternalScheme(link: string): vscode.Uri | undefined { + if (knownSchemes.some(knownScheme => isOfScheme(knownScheme, link))) { return vscode.Uri.parse(link); } return undefined; } + +export function isOfScheme(scheme: string, link: string): boolean { + return link.toLowerCase().startsWith(scheme); +} diff --git a/extensions/markdown-language-features/yarn.lock b/extensions/markdown-language-features/yarn.lock index a45f00885e..ccaebc2cbe 100644 --- a/extensions/markdown-language-features/yarn.lock +++ b/extensions/markdown-language-features/yarn.lock @@ -1973,10 +1973,10 @@ enhanced-resolve@^4.0.0: memory-fs "^0.4.0" tapable "^1.0.0" -entities@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" - integrity sha1-blwtClYhtdra7O+AuQ7ftc13cvA= +entities@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" + integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== errno@^0.1.3, errno@~0.1.7: version "0.1.7" @@ -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.15.8: - version "9.15.8" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.15.8.tgz#f344fda123f36f1a65490e932cf90569e4999971" - integrity sha512-RrapkKQWwE+wKdF73VsOa2RQdIoO3mxwJ4P8mhbI6KYJUraUHRKM5w5zQQKXNk0xNL4UVRdulV9SBJcmzJNzVA== +highlight.js@9.15.10: + version "9.15.10" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.15.10.tgz#7b18ed75c90348c045eef9ed08ca1319a2219ad2" + integrity sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw== hmac-drbg@^1.0.0: version "1.0.1" @@ -3900,13 +3900,13 @@ markdown-it-front-matter@^0.1.2: resolved "https://registry.yarnpkg.com/markdown-it-front-matter/-/markdown-it-front-matter-0.1.2.tgz#e50bf56e77e6a4f5ac4ffa894d4d45ccd9896b20" integrity sha1-5Qv1bnfmpPWsT/qJTU1FzNmJayA= -markdown-it@^9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-9.1.0.tgz#df9601c168568704d554b1fff9af0c5b561168d9" - integrity sha512-xHKG4C8iPriyfu/jc2hsCC045fKrMQ0VexX2F1FGYiRxDxqMB2aAhF8WauJ3fltn2kb90moGBkiiEdooGIg55w== +markdown-it@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc" + integrity sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg== dependencies: argparse "^1.0.7" - entities "~1.1.1" + entities "~2.0.0" linkify-it "^2.0.0" mdurl "^1.0.1" uc.micro "^1.0.5" diff --git a/extensions/theme-abyss/themes/abyss-color-theme.json b/extensions/theme-abyss/themes/abyss-color-theme.json index 18c8235f79..f0a326bb34 100644 --- a/extensions/theme-abyss/themes/abyss-color-theme.json +++ b/extensions/theme-abyss/themes/abyss-color-theme.json @@ -3,14 +3,12 @@ "tokenColors": [ { "settings": { - "background": "#000c18", "foreground": "#6688cc" } }, { "scope": ["meta.embedded", "source.groovy.embedded"], "settings": { - "background": "#000c18", "foreground": "#6688cc" } }, @@ -173,7 +171,6 @@ "name": "Invalid", "scope": "invalid", "settings": { - "background": "#F92672", "fontStyle": "", "foreground": "#F8F8F0" } @@ -182,7 +179,6 @@ "name": "Invalid deprecated", "scope": "invalid.deprecated", "settings": { - "background": "#AE81FF", "foreground": "#F8F8F0" } }, @@ -193,7 +189,6 @@ "meta.diff.header" ], "settings": { - "background": "#b58900", "fontStyle": "italic", "foreground": "#E0EDDD" } @@ -202,7 +197,6 @@ "name": "diff: deleted", "scope": "markup.deleted", "settings": { - "background": "#eee8d5", "fontStyle": "", "foreground": "#dc322f" } @@ -211,7 +205,6 @@ "name": "diff: changed", "scope": "markup.changed", "settings": { - "background": "#eee8d5", "fontStyle": "", "foreground": "#cb4b16" } @@ -220,7 +213,6 @@ "name": "diff: inserted", "scope": "markup.inserted", "settings": { - "background": "#eee8d5", "foreground": "#219186" } }, @@ -378,7 +370,6 @@ "tab.border": "#2b2b4a", // "tab.activeBackground": "", "tab.inactiveBackground": "#10192c", - "tab.modifiedBorder": "#0072bf", // "tab.activeForeground": "", // "tab.inactiveForeground": "", 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 d3a76435f6..111a4a23d9 100644 --- a/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json +++ b/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json @@ -23,7 +23,6 @@ "editorGroupHeader.tabsBackground": "#131510", "editorLineNumber.activeForeground": "#adadad", "tab.inactiveBackground": "#131510", - "tab.modifiedBorder": "#cd9731", "titleBar.activeBackground": "#423523", "statusBar.background": "#423523", "statusBar.debuggingBackground": "#423523", @@ -55,14 +54,12 @@ "tokenColors": [ { "settings": { - "background": "#221a0f", "foreground": "#d3af86" } }, { "scope": ["meta.embedded", "source.groovy.embedded"], "settings": { - "background": "#221a0f", "foreground": "#d3af86" } }, @@ -335,7 +332,6 @@ "name": "Separator", "scope": "meta.separator", "settings": { - "background": "#84613d", "foreground": "#d3af86" } }, 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 0a5851352b..f0b6126d5f 100644 --- a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json +++ b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json @@ -24,7 +24,6 @@ "editorGroupHeader.tabsBackground": "#282828", "tab.inactiveBackground": "#404040", "tab.border": "#303030", - "tab.modifiedBorder": "#6796e6", "tab.inactiveForeground": "#d8d8d8", "peekView.border": "#3655b5", "panelTitle.activeForeground": "#ffffff", @@ -48,24 +47,15 @@ "tokenColors": [ { "settings": { - "background": "#1e1e1e", "foreground": "#C5C8C6" } }, - { - "name": "By uonick", - "settings": { - "background": "#202025ff", - "foreground": "#c5c8c6ff" - } - }, { "scope": [ "meta.embedded", "source.groovy.embedded" ], "settings": { - "background": "#1e1e1e", "foreground": "#C5C8C6" } }, @@ -115,7 +105,6 @@ "settings": { "fontStyle": "", "foreground": "#8080FF", - "background": "#1e1e1e" } }, { @@ -148,7 +137,6 @@ "settings": { "fontStyle": "", "foreground": "#9B0000", - "background": "#1E1E1E" } }, { @@ -482,7 +470,6 @@ "name": "diff: header", "scope": "meta.diff, meta.diff.header", "settings": { - "background": "#b58900", "fontStyle": "italic", "foreground": "#E0EDDD" } @@ -491,7 +478,6 @@ "name": "diff: deleted", "scope": "markup.deleted", "settings": { - "background": "#eee8d5", "fontStyle": "", "foreground": "#dc322f" } @@ -500,7 +486,6 @@ "name": "diff: changed", "scope": "markup.changed", "settings": { - "background": "#eee8d5", "fontStyle": "", "foreground": "#cb4b16" } @@ -509,7 +494,6 @@ "name": "diff: inserted", "scope": "markup.inserted", "settings": { - "background": "#eee8d5", "foreground": "#219186" } }, diff --git a/extensions/theme-monokai/themes/monokai-color-theme.json b/extensions/theme-monokai/themes/monokai-color-theme.json index ebff3a4498..6bc2e84ce9 100644 --- a/extensions/theme-monokai/themes/monokai-color-theme.json +++ b/extensions/theme-monokai/themes/monokai-color-theme.json @@ -36,7 +36,6 @@ "editorGroup.dropBackground": "#41433980", "tab.inactiveBackground": "#34352f", "tab.border": "#1e1f1c", - "tab.modifiedBorder": "#007acc", "tab.inactiveForeground": "#ccccc7", // needs to be bright so it's readable when another editor group is focused "widget.shadow": "#000000", "progressBar.background": "#75715E", @@ -65,8 +64,8 @@ "focusBorder": "#75715E", "editorWidget.background": "#1e1f1c", "debugToolBar.background": "#1e1f1c", - "diffEditor.insertedTextBackground": "#66852880", // middle of #272822 and #a6e22e - "diffEditor.removedTextBackground": "#90274A80", // middle of #272822 and #f92672 + "diffEditor.insertedTextBackground": "#4b661680", // middle of #272822 and #a6e22e + "diffEditor.removedTextBackground": "#90274A70", // middle of #272822 and #f92672 "inputValidation.errorBackground": "#90274A", // middle of #272822 and #f92672 "inputValidation.errorBorder": "#f92672", "inputValidation.warningBackground": "#848528", // middle of #272822 and #e2e22e @@ -105,7 +104,6 @@ "tokenColors": [ { "settings": { - "background": "#272822", "foreground": "#F8F8F2" } }, @@ -115,7 +113,6 @@ "source.groovy.embedded" ], "settings": { - "background": "#272822", "foreground": "#F8F8F2" } }, @@ -123,7 +120,7 @@ "name": "Comment", "scope": "comment", "settings": { - "foreground": "#75715E" + "foreground": "#88846f" } }, { @@ -287,7 +284,6 @@ "name": "Invalid", "scope": "invalid", "settings": { - "background": "#F92672", "fontStyle": "", "foreground": "#F8F8F0" } @@ -296,7 +292,6 @@ "name": "Invalid deprecated", "scope": "invalid.deprecated", "settings": { - "background": "#AE81FF", "foreground": "#F8F8F0" } }, diff --git a/extensions/theme-quietlight/themes/quietlight-color-theme.json b/extensions/theme-quietlight/themes/quietlight-color-theme.json index 355924fea2..ae19ba7889 100644 --- a/extensions/theme-quietlight/themes/quietlight-color-theme.json +++ b/extensions/theme-quietlight/themes/quietlight-color-theme.json @@ -3,7 +3,6 @@ "tokenColors": [ { "settings": { - "background": "#F5F5F5", "foreground": "#333333" } }, @@ -13,7 +12,6 @@ "source.groovy.embedded" ], "settings": { - "background": "#F5F5F5", "foreground": "#333333" } }, @@ -47,18 +45,10 @@ "foreground": "#448C27" } }, - { - "name": "Invalid - Deprecated", - "scope": "invalid.deprecated", - "settings": { - "background": "#96000014" - } - }, { "name": "Invalid - Illegal", "scope": "invalid.illegal", "settings": { - "background": "#96000014", "foreground": "#660000" } }, @@ -195,20 +185,6 @@ "foreground": "#777777" } }, - { - "name": "Embedded Source", - "scope": [ - "string source", - "text source" - ], - "settings": { - "background": "#EAEBE6" - } - }, - { - "name": "-----------------------------------", - "settings": {} - }, { "name": "HTML: Doctype Declaration", "scope": [ @@ -261,10 +237,6 @@ "foreground": "#9C5D27" } }, - { - "name": "-----------------------------------", - "settings": {} - }, { "name": "CSS: Selectors", "scope": [ @@ -305,15 +277,10 @@ "fontStyle": "bold" } }, - { - "name": "-----------------------------------", - "settings": {} - }, { "name": "Markup: Changed", "scope": "markup.changed", "settings": { - "background": "#FFFFDD", "foreground": "#000000" } }, @@ -321,7 +288,6 @@ "name": "Markup: Deletion", "scope": "markup.deleted", "settings": { - "background": "#FFDDDD", "foreground": "#000000" } }, @@ -336,7 +302,6 @@ "name": "Markup: Error", "scope": "markup.error", "settings": { - "background": "#96000014", "foreground": "#660000" } }, @@ -344,7 +309,6 @@ "name": "Markup: Insertion", "scope": "markup.inserted", "settings": { - "background": "#DDFFDD", "foreground": "#000000" } }, @@ -432,10 +396,6 @@ "foreground": "#9C5D27" } }, - { - "name": "-----------------------------------", - "settings": {} - }, { "name": "Extra: Diff Range", "scope": [ @@ -444,7 +404,6 @@ "meta.separator" ], "settings": { - "background": "#DDDDFF", "foreground": "#434343" } }, @@ -452,7 +411,6 @@ "name": "Extra: Diff From", "scope": "meta.diff.header.from-file", "settings": { - "background": "#FFDDDD", "foreground": "#434343" } }, @@ -460,7 +418,6 @@ "name": "Extra: Diff To", "scope": "meta.diff.header.to-file", "settings": { - "background": "#DDFFDD", "foreground": "#434343" } }, @@ -500,7 +457,6 @@ "editorLineNumber.activeForeground": "#9769dc", "editor.selectionBackground": "#C9D0D9", "minimap.selectionHighlight": "#C9D0D9", - "tab.modifiedBorder": "#f1897f", "panel.background": "#F5F5F5", "sideBar.background": "#F2F2F2", "sideBarSectionHeader.background": "#ede8ef", diff --git a/extensions/theme-red/themes/Red-color-theme.json b/extensions/theme-red/themes/Red-color-theme.json index 309a4273fb..277e7a8db3 100644 --- a/extensions/theme-red/themes/Red-color-theme.json +++ b/extensions/theme-red/themes/Red-color-theme.json @@ -5,7 +5,6 @@ "activityBar.background": "#580000", "tab.inactiveBackground": "#300a0a", "tab.activeBackground": "#490000", - "tab.modifiedBorder": "#db7e58", "sideBar.background": "#330000", "statusBar.background": "#700000", "statusBar.noFolderBackground": "#700000", @@ -62,12 +61,7 @@ "tokenColors": [ { "settings": { - "background": "#390000", - "caret": "#970000", "foreground": "#F8F8F8", - "invisibles": "#c10000", - "lineHighlight": "#0000004A", - "selection": "#750000" } }, { @@ -76,7 +70,6 @@ "source.groovy.embedded" ], "settings": { - "background": "#390000", "foreground": "#F8F8F8" } }, @@ -148,24 +141,9 @@ "name": "Invalid", "scope": "invalid", "settings": { - "background": "#fd6209ff", "foreground": "#ffffffff" } }, - { - "name": "Embedded Source", - "scope": "text source", - "settings": { - "background": "#b0b3ba14" - } - }, - { - "name": "Embedded Source (Bright)", - "scope": "text.html.ruby source", - "settings": { - "background": "#b1b3ba21" - } - }, { "name": "Entity inherited-class", "scope": "entity.other.inherited-class", @@ -323,7 +301,6 @@ "meta.diff.header" ], "settings": { - "background": "#0b2f20ff", "fontStyle": "italic", "foreground": "#f8f8f8ff" } @@ -332,7 +309,6 @@ "name": "diff.deleted", "scope": "markup.deleted", "settings": { - "background": "#fedcddff", "foreground": "#ec9799ff" } }, @@ -340,7 +316,6 @@ "name": "diff.changed", "scope": "markup.changed", "settings": { - "background": "#c4b14aff", "foreground": "#f8f8f8ff" } }, @@ -348,7 +323,6 @@ "name": "diff.inserted", "scope": "markup.inserted", "settings": { - "background": "#9bf199ff", "foreground": "#41a83eff" } }, 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 0d6851d531..682444485d 100644 --- a/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json +++ b/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json @@ -3,14 +3,12 @@ "tokenColors": [ { "settings": { - "background": "#002B36", "foreground": "#93A1A1" } }, { "scope": ["meta.embedded", "source.groovy.embedded"], "settings": { - "background": "#002B36", "foreground": "#93A1A1" } }, @@ -221,7 +219,6 @@ "meta.diff.header" ], "settings": { - "background": "#b58900", "fontStyle": "italic", "foreground": "#E0EDDD" } @@ -230,7 +227,6 @@ "name": "diff: deleted", "scope": "markup.deleted", "settings": { - "background": "#eee8d5", "fontStyle": "", "foreground": "#dc322f" } @@ -239,7 +235,6 @@ "name": "diff: changed", "scope": "markup.changed", "settings": { - "background": "#eee8d5", "fontStyle": "", "foreground": "#cb4b16" } @@ -248,7 +243,6 @@ "name": "diff: inserted", "scope": "markup.inserted", "settings": { - "background": "#eee8d5", "foreground": "#219186" } }, @@ -425,7 +419,6 @@ "tab.inactiveForeground": "#93A1A1", "tab.inactiveBackground": "#004052", "tab.border": "#003847", - "tab.modifiedBorder": "#268bd2", // Workbench: Activity Bar "activityBar.background": "#003847", 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 b7020f8e7c..a29c8fb32f 100644 --- a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json +++ b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json @@ -3,14 +3,12 @@ "tokenColors": [ { "settings": { - "background": "#FDF6E3", "foreground": "#657B83" } }, { "scope": ["meta.embedded", "source.groovy.embedded"], "settings": { - "background": "#FDF6E3", "foreground": "#657B83" } }, diff --git a/package.json b/package.json index c270294271..bf7bf336df 100644 --- a/package.json +++ b/package.json @@ -18,16 +18,18 @@ "monaco-editor-test": "mocha --only-monaco-editor", "precommit": "node build/gulpfile.hygiene.js", "gulp": "gulp --max_old_space_size=8192", + "electron": "node build/lib/electron", "7z": "7z", "update-grammars": "node build/npm/update-all-grammars.js", "update-localization-extension": "node build/npm/update-localization-extension.js", "smoketest": "cd test/smoke && node test/index.js", "monaco-compile-check": "tsc -p src/tsconfig.monaco.json --noEmit", "tslint": "node node_modules/tslint/bin/tslint -c tslint-gci.json -p src/tsconfig.json", - "strict-null-check": "tsc -p src/tsconfig.strictNullChecks.json", - "strict-null-check-watch": "tsc -p src/tsconfig.strictNullChecks.json --watch", + "strict-null-check": "node node_modules/typescript/bin/tsc -p src/tsconfig.strictNullChecks.json", + "strict-null-check-watch": "node node_modules/typescript/bin/tsc -p src/tsconfig.strictNullChecks.json --watch", "strict-initialization-watch": "tsc --watch -p src/tsconfig.json --noEmit --strictPropertyInitialization", - "update-distro": "node build/npm/update-distro.js" + "update-distro": "node build/npm/update-distro.js", + "web": "node scripts/code-web.js" }, "dependencies": { "@angular/animations": "~4.1.3", @@ -38,13 +40,12 @@ "@angular/platform-browser": "~4.1.3", "@angular/platform-browser-dynamic": "~4.1.3", "@angular/router": "~4.1.3", - "@microsoft/applicationinsights-web": "^2.1.1", "angular2-grid": "2.0.6", "angular2-slickgrid": "github:Microsoft/angular2-slickgrid#1.4.6", "ansi_up": "^3.0.0", "applicationinsights": "1.0.8", "chart.js": "^2.6.0", - "chokidar": "3.1.0", + "chokidar": "3.2.2", "graceful-fs": "4.1.11", "html-query-plan": "git://github.com/anthonydresser/html-query-plan.git#2.6", "http-proxy-agent": "^2.1.0", @@ -75,9 +76,9 @@ "vscode-ripgrep": "^1.5.7", "vscode-sqlite3": "4.0.8", "vscode-textmate": "^4.2.2", - "xterm": "4.1.0-beta8", - "xterm-addon-search": "0.2.0", - "xterm-addon-web-links": "0.2.0", + "xterm": "^4.2.0-beta20", + "xterm-addon-search": "0.3.0-beta5", + "xterm-addon-web-links": "0.2.1", "yauzl": "^2.9.2", "yazl": "^2.4.3", "zone.js": "^0.8.4" @@ -126,7 +127,7 @@ "gulp-rename": "^1.2.0", "gulp-replace": "^0.5.4", "gulp-shell": "^0.6.5", - "gulp-tsb": "4.0.4", + "gulp-tsb": "4.0.5", "gulp-tslint": "^8.1.3", "gulp-untar": "^0.0.7", "gulp-vinyl-zip": "^2.1.2", @@ -161,7 +162,7 @@ "tslint": "^5.16.0", "tslint-microsoft-contrib": "^6.0.0", "typemoq": "^0.3.2", - "typescript": "3.6", + "typescript": "3.7.0-dev.20191017", "typescript-formatter": "7.1.0", "vinyl": "^2.0.0", "vinyl-fs": "^3.0.0", diff --git a/remote/.yarnrc b/remote/.yarnrc index b28191e6ba..1e16cde724 100644 --- a/remote/.yarnrc +++ b/remote/.yarnrc @@ -1,3 +1,3 @@ disturl "http://nodejs.org/dist" -target "10.11.0" +target "12.4.0" runtime "node" diff --git a/remote/package.json b/remote/package.json index 10c377499f..fd526a0b24 100644 --- a/remote/package.json +++ b/remote/package.json @@ -2,9 +2,8 @@ "name": "vscode-reh", "version": "0.0.0", "dependencies": { - "@microsoft/applicationinsights-web": "^2.1.1", "applicationinsights": "1.0.8", - "chokidar": "3.1.0", + "chokidar": "3.2.2", "cookie": "^0.4.0", "graceful-fs": "4.1.11", "http-proxy-agent": "^2.1.0", @@ -21,9 +20,9 @@ "vscode-proxy-agent": "0.4.0", "vscode-ripgrep": "^1.5.7", "vscode-textmate": "^4.2.2", - "xterm": "4.1.0-beta8", - "xterm-addon-search": "0.2.0", - "xterm-addon-web-links": "0.2.0", + "xterm": "^4.2.0-beta20", + "xterm-addon-search": "0.3.0-beta5", + "xterm-addon-web-links": "0.2.1", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/web/package.json b/remote/web/package.json index ab86c010ea..cf40cc22b1 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -2,12 +2,11 @@ "name": "vscode-web", "version": "0.0.0", "dependencies": { - "@microsoft/applicationinsights-web": "^2.1.1", "onigasm-umd": "^2.2.2", "semver-umd": "^5.5.3", "vscode-textmate": "^4.2.2", - "xterm": "4.1.0-beta8", - "xterm-addon-search": "0.2.0", - "xterm-addon-web-links": "0.2.0" + "xterm": "^4.2.0-beta20", + "xterm-addon-search": "0.3.0-beta5", + "xterm-addon-web-links": "0.2.1" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index ccc09170f6..f3ca259409 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -2,69 +2,6 @@ # yarn lockfile v1 -"@microsoft/applicationinsights-analytics-js@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-2.1.1.tgz#6d09c1915f808026e2d45165d04802f09affed59" - integrity sha512-VKIutoFKY99CyKwxLUuj6Vnq14/QwXo9/QSQDpYnHEjo+uKn7QmLsHqWw0K9uYNfNAXt4BZimX/zDg6jZtzeXg== - dependencies: - "@microsoft/applicationinsights-common" "2.1.1" - "@microsoft/applicationinsights-core-js" "2.1.1" - tslib "^1.9.3" - -"@microsoft/applicationinsights-channel-js@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.1.1.tgz#e205eddd93e49d17d9e0711a612b4bfc9810888f" - integrity sha512-fYr9IAqtaEr9AmaPaL3SLQVT3t3GQzl+n74gpNKyAVakDIm0nYQ/bimjdcAhJMDf1VGNSPg/xICneyuZg7Wxlg== - dependencies: - "@microsoft/applicationinsights-common" "2.1.1" - "@microsoft/applicationinsights-core-js" "2.1.1" - tslib "^1.9.3" - -"@microsoft/applicationinsights-common@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-common/-/applicationinsights-common-2.1.1.tgz#27e6074584a7a3a8ca3f11f7ff2b7ff0f395bf2d" - integrity sha512-2hkS1Ia1FmAjCuYZ5JlG20/WgObqdsKtmK5YALAFGHIB4KSQ/Za1qazS+7GsG+E0F9UJivNWL1geUIcNqg5Qjg== - dependencies: - "@microsoft/applicationinsights-core-js" "2.1.1" - tslib "^1.9.3" - -"@microsoft/applicationinsights-core-js@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.1.1.tgz#30fb6a519cc1c6119c419c4811ce72c260217d9e" - integrity sha512-4t4wf6SKqIcWEQDPg/uOhm+BxtHhu/AFreyEoYZmMfcxzAu33h1FtTQRtxBNbYH1+thiNZCh80yUpnT7d9Hrlw== - dependencies: - tslib "^1.9.3" - -"@microsoft/applicationinsights-dependencies-js@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-2.1.1.tgz#8154c3efcb24617d015d0bce7c2cc47797a8d3c4" - integrity sha512-yhb4EToBp+aI+qLo0h5NDNtoo3sDFV60uyIOK843YjzXqVotcXX/lRShlghTkJtYH09QhrdzDjViUHnD4sMFSQ== - dependencies: - "@microsoft/applicationinsights-common" "2.1.1" - "@microsoft/applicationinsights-core-js" "2.1.1" - tslib "^1.9.3" - -"@microsoft/applicationinsights-properties-js@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-2.1.1.tgz#ca34232766eb16167b5d87693e2ae5d94f2a1559" - integrity sha512-8l+/ppw6xKTam2RL4EHZ52Lcf217olw81j6kyBNKtIcGwSnLNHrFwEeF3vBWIteG2JKzlg1GhGjrkB3oxXsV2g== - dependencies: - "@microsoft/applicationinsights-common" "2.1.1" - "@microsoft/applicationinsights-core-js" "2.1.1" - tslib "^1.9.3" - -"@microsoft/applicationinsights-web@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web/-/applicationinsights-web-2.1.1.tgz#1a44eddda7c244b88d9eb052dab6c855682e4f05" - integrity sha512-crvhCkNsNxkFuPWmttyWNSAA96D5FxBtKS6UA9MV9f9XHevTfchf/E3AuU9JZcsXufWMQLwLrUQ9ZiA1QJ0EWA== - dependencies: - "@microsoft/applicationinsights-analytics-js" "2.1.1" - "@microsoft/applicationinsights-channel-js" "2.1.1" - "@microsoft/applicationinsights-common" "2.1.1" - "@microsoft/applicationinsights-core-js" "2.1.1" - "@microsoft/applicationinsights-dependencies-js" "2.1.1" - "@microsoft/applicationinsights-properties-js" "2.1.1" - nan@^2.14.0: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" @@ -87,11 +24,6 @@ semver-umd@^5.5.3: resolved "https://registry.yarnpkg.com/semver-umd/-/semver-umd-5.5.3.tgz#b64d7a2d4f5a717b369d56e31940a38e47e34d1e" integrity sha512-HOnQrn2iKnVe/xlqCTzMXQdvSz3rPbD0DmQXYuQ+oK1dpptGFfPghonQrx5JHl2O7EJwDqtQnjhE7ME23q6ngw== -tslib@^1.9.3: - version "1.10.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" - integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== - vscode-textmate@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.2.2.tgz#0b4dabc69a6fba79a065cb6b615f66eac07c8f4c" @@ -99,17 +31,17 @@ vscode-textmate@^4.2.2: dependencies: oniguruma "^7.2.0" -xterm-addon-search@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.2.0.tgz#46659c7c33f9fc268ad3e7e6c5824bb2fdb65852" - integrity sha512-C/v2VvFn3hb1qUgjJPo7LxzxNCLBgNJv8n6v/bH2NqPz32/PNUF+IHu0SFf1TaIH+pydUpKXCtob5a/UyZg/+Q== +xterm-addon-search@0.3.0-beta5: + version "0.3.0-beta5" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.3.0-beta5.tgz#fd53d33a77a0235018479c712be8c12f7c0d083a" + integrity sha512-3GkGc4hST35/4hzgnQPLLvQ29WH7MkZ0mUrBE/Vm1IQum7TnMvWPTkGemwM+wAl4tdBmynNccHJlFeQzaQtVUg== -xterm-addon-web-links@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.2.0.tgz#b408a0be46211d8d4a0bb5e701d8f3c2bd07d473" - integrity sha512-dq81c4Pzli2PgKVBgY2REte9sCVibR3df8AP3SEvCTM9uYFnUFxtxzMTplPnc7+rXabVhFdbU6x+rstIk8HNQg== +xterm-addon-web-links@0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.2.1.tgz#6d1f2ce613e09870badf17615e7a1170a31542b2" + integrity sha512-2KnHtiq0IG7hfwv3jw2/jQeH1RBk2d5CH4zvgwQe00rLofSJqSfgnJ7gwowxxpGHrpbPr6Lv4AmH/joaNw2+HQ== -xterm@4.1.0-beta8: - version "4.1.0-beta8" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.1.0-beta8.tgz#c1ef323ba336d92f5b52302b66f672dfff75b3ef" - integrity sha512-6lf+XVv0qT285w49P92tSYoUB406jdbgdhnPKNzxCIGtGX8kcwK+pHZ8HncDwcEhmTmI4LZ/WXPGtOQJg+onwg== +xterm@^4.2.0-beta20: + version "4.2.0-beta20" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.2.0-beta20.tgz#74a759414511b4577159293397914ac33a2375d8" + integrity sha512-lH52ksaNQqWmLVV4OdLKWvhGkSRUXgJNvb2YYmVkBAm1PdVVS36ir8Qr4zYygnc2tBw689Wj65t4cNckmfpU1Q== diff --git a/remote/yarn.lock b/remote/yarn.lock index 052c1fa784..8fb560f3ee 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -2,69 +2,6 @@ # yarn lockfile v1 -"@microsoft/applicationinsights-analytics-js@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-2.1.1.tgz#6d09c1915f808026e2d45165d04802f09affed59" - integrity sha512-VKIutoFKY99CyKwxLUuj6Vnq14/QwXo9/QSQDpYnHEjo+uKn7QmLsHqWw0K9uYNfNAXt4BZimX/zDg6jZtzeXg== - dependencies: - "@microsoft/applicationinsights-common" "2.1.1" - "@microsoft/applicationinsights-core-js" "2.1.1" - tslib "^1.9.3" - -"@microsoft/applicationinsights-channel-js@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.1.1.tgz#e205eddd93e49d17d9e0711a612b4bfc9810888f" - integrity sha512-fYr9IAqtaEr9AmaPaL3SLQVT3t3GQzl+n74gpNKyAVakDIm0nYQ/bimjdcAhJMDf1VGNSPg/xICneyuZg7Wxlg== - dependencies: - "@microsoft/applicationinsights-common" "2.1.1" - "@microsoft/applicationinsights-core-js" "2.1.1" - tslib "^1.9.3" - -"@microsoft/applicationinsights-common@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-common/-/applicationinsights-common-2.1.1.tgz#27e6074584a7a3a8ca3f11f7ff2b7ff0f395bf2d" - integrity sha512-2hkS1Ia1FmAjCuYZ5JlG20/WgObqdsKtmK5YALAFGHIB4KSQ/Za1qazS+7GsG+E0F9UJivNWL1geUIcNqg5Qjg== - dependencies: - "@microsoft/applicationinsights-core-js" "2.1.1" - tslib "^1.9.3" - -"@microsoft/applicationinsights-core-js@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.1.1.tgz#30fb6a519cc1c6119c419c4811ce72c260217d9e" - integrity sha512-4t4wf6SKqIcWEQDPg/uOhm+BxtHhu/AFreyEoYZmMfcxzAu33h1FtTQRtxBNbYH1+thiNZCh80yUpnT7d9Hrlw== - dependencies: - tslib "^1.9.3" - -"@microsoft/applicationinsights-dependencies-js@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-2.1.1.tgz#8154c3efcb24617d015d0bce7c2cc47797a8d3c4" - integrity sha512-yhb4EToBp+aI+qLo0h5NDNtoo3sDFV60uyIOK843YjzXqVotcXX/lRShlghTkJtYH09QhrdzDjViUHnD4sMFSQ== - dependencies: - "@microsoft/applicationinsights-common" "2.1.1" - "@microsoft/applicationinsights-core-js" "2.1.1" - tslib "^1.9.3" - -"@microsoft/applicationinsights-properties-js@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-2.1.1.tgz#ca34232766eb16167b5d87693e2ae5d94f2a1559" - integrity sha512-8l+/ppw6xKTam2RL4EHZ52Lcf217olw81j6kyBNKtIcGwSnLNHrFwEeF3vBWIteG2JKzlg1GhGjrkB3oxXsV2g== - dependencies: - "@microsoft/applicationinsights-common" "2.1.1" - "@microsoft/applicationinsights-core-js" "2.1.1" - tslib "^1.9.3" - -"@microsoft/applicationinsights-web@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web/-/applicationinsights-web-2.1.1.tgz#1a44eddda7c244b88d9eb052dab6c855682e4f05" - integrity sha512-crvhCkNsNxkFuPWmttyWNSAA96D5FxBtKS6UA9MV9f9XHevTfchf/E3AuU9JZcsXufWMQLwLrUQ9ZiA1QJ0EWA== - dependencies: - "@microsoft/applicationinsights-analytics-js" "2.1.1" - "@microsoft/applicationinsights-channel-js" "2.1.1" - "@microsoft/applicationinsights-common" "2.1.1" - "@microsoft/applicationinsights-core-js" "2.1.1" - "@microsoft/applicationinsights-dependencies-js" "2.1.1" - "@microsoft/applicationinsights-properties-js" "2.1.1" - agent-base@4, agent-base@^4.1.0: version "4.2.0" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce" @@ -79,10 +16,10 @@ agent-base@~4.2.0: dependencies: es6-promisify "^5.0.0" -anymatch@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.0.tgz#e609350e50a9313b472789b2f14ef35808ee14d6" - integrity sha512-Ozz7l4ixzI7Oxj2+cw+p0tVUt27BpaJ+1+q1TCeANWxHpvyn2+Un+YamBdfKu0uh8xLodGhoa1v7595NhKDAuA== +anymatch@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== dependencies: normalize-path "^3.0.0" picomatch "^2.0.4" @@ -108,7 +45,7 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" -braces@^3.0.2: +braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -120,20 +57,20 @@ buffer-crc32@~0.2.3: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= -chokidar@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.1.0.tgz#ff23d077682a90eadd209bfa76eb10ed6d359668" - integrity sha512-6vZfo+7W0EOlbSo0nhVKMz4yyssrwiPbBZ8wj1lq8/+l4ZhGZ2U4Md7PspvmijXp1a26D3B7AHEBmIB7aVtaOQ== +chokidar@3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.2.2.tgz#a433973350021e09f2b853a2287781022c0dc935" + integrity sha512-bw3pm7kZ2Wa6+jQWYP/c7bAZy3i4GwiIiMO2EeRjrE48l8vBqC/WvFhSF0xyM8fQiPEGvwMY/5bqDG7sSEOuhg== dependencies: - anymatch "^3.1.0" - braces "^3.0.2" - glob-parent "^5.0.0" - is-binary-path "^2.1.0" - is-glob "^4.0.1" - normalize-path "^3.0.0" - readdirp "^3.1.1" + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.2.0" optionalDependencies: - fsevents "^2.0.6" + fsevents "~2.1.1" cookie@^0.4.0: version "0.4.0" @@ -199,15 +136,15 @@ fs-extra@^7.0.0: jsonfile "^4.0.0" universalify "^0.1.0" -fsevents@^2.0.6: - version "2.0.7" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.0.7.tgz#382c9b443c6cbac4c57187cdda23aa3bf1ccfc2a" - integrity sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ== +fsevents@~2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.1.tgz#74c64e21df71721845d0c44fe54b7f56b82995a9" + integrity sha512-4FRPXWETxtigtJW/gxzEDsX1LVbPAM93VleB83kZB+ellqbHMkyt2aJfuzNLRvFPnGi6bcE5SvfxgbXPeKteJw== -glob-parent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.0.0.tgz#1dc99f0f39b006d3e92c2c284068382f0c20e954" - integrity sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg== +glob-parent@~5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" + integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== dependencies: is-glob "^4.0.1" @@ -249,7 +186,7 @@ ip@^1.1.5: resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= -is-binary-path@^2.1.0: +is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== @@ -261,7 +198,7 @@ is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-glob@^4.0.1: +is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== @@ -334,7 +271,7 @@ node-pty@0.9.0-beta19: dependencies: nan "^2.13.2" -normalize-path@^3.0.0: +normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== @@ -371,10 +308,10 @@ picomatch@^2.0.4: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6" integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA== -readdirp@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.1.2.tgz#fa85d2d14d4289920e4671dead96431add2ee78a" - integrity sha512-8rhl0xs2cxfVsqzreYCvs8EwBfn/DhVdqtoLmw19uI3SC5avYX9teCurlErfpPXGmYtMHReGaP2RsLnFvz/lnw== +readdirp@~3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" + integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ== dependencies: picomatch "^2.0.4" @@ -430,11 +367,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -tslib@^1.9.3: - version "1.10.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" - integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== - universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -479,20 +411,20 @@ vscode-windows-registry@1.0.2: resolved "https://registry.yarnpkg.com/vscode-windows-registry/-/vscode-windows-registry-1.0.2.tgz#b863e704a6a69c50b3098a55fbddbe595b0c124a" integrity sha512-/CLLvuOSM2Vme2z6aNyB+4Omd7hDxpf4Thrt8ImxnXeQtxzel2bClJpFQvQqK/s4oaXlkBKS7LqVLeZM+uSVIA== -xterm-addon-search@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.2.0.tgz#46659c7c33f9fc268ad3e7e6c5824bb2fdb65852" - integrity sha512-C/v2VvFn3hb1qUgjJPo7LxzxNCLBgNJv8n6v/bH2NqPz32/PNUF+IHu0SFf1TaIH+pydUpKXCtob5a/UyZg/+Q== +xterm-addon-search@0.3.0-beta5: + version "0.3.0-beta5" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.3.0-beta5.tgz#fd53d33a77a0235018479c712be8c12f7c0d083a" + integrity sha512-3GkGc4hST35/4hzgnQPLLvQ29WH7MkZ0mUrBE/Vm1IQum7TnMvWPTkGemwM+wAl4tdBmynNccHJlFeQzaQtVUg== -xterm-addon-web-links@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.2.0.tgz#b408a0be46211d8d4a0bb5e701d8f3c2bd07d473" - integrity sha512-dq81c4Pzli2PgKVBgY2REte9sCVibR3df8AP3SEvCTM9uYFnUFxtxzMTplPnc7+rXabVhFdbU6x+rstIk8HNQg== +xterm-addon-web-links@0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.2.1.tgz#6d1f2ce613e09870badf17615e7a1170a31542b2" + integrity sha512-2KnHtiq0IG7hfwv3jw2/jQeH1RBk2d5CH4zvgwQe00rLofSJqSfgnJ7gwowxxpGHrpbPr6Lv4AmH/joaNw2+HQ== -xterm@4.1.0-beta8: - version "4.1.0-beta8" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.1.0-beta8.tgz#c1ef323ba336d92f5b52302b66f672dfff75b3ef" - integrity sha512-6lf+XVv0qT285w49P92tSYoUB406jdbgdhnPKNzxCIGtGX8kcwK+pHZ8HncDwcEhmTmI4LZ/WXPGtOQJg+onwg== +xterm@^4.2.0-beta20: + version "4.2.0-beta20" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.2.0-beta20.tgz#74a759414511b4577159293397914ac33a2375d8" + integrity sha512-lH52ksaNQqWmLVV4OdLKWvhGkSRUXgJNvb2YYmVkBAm1PdVVS36ir8Qr4zYygnc2tBw689Wj65t4cNckmfpU1Q== yauzl@^2.9.2: version "2.10.0" diff --git a/resources/linux/bin/code.sh b/resources/linux/bin/code.sh index 0cf035d2ad..96aa59e3d8 100755 --- a/resources/linux/bin/code.sh +++ b/resources/linux/bin/code.sh @@ -30,7 +30,7 @@ if [ ! -L $0 ]; then # if path is not a symlink, find relatively VSCODE_PATH="$(dirname $0)/.." else - if which readlink >/dev/null; then + if command -v readlink >/dev/null; then # if readlink exists, follow the symlink and find relatively VSCODE_PATH="$(dirname $(readlink -f $0))/.." else diff --git a/resources/linux/code-url-handler.desktop b/resources/linux/code-url-handler.desktop index b8e921920b..caf34337a3 100644 --- a/resources/linux/code-url-handler.desktop +++ b/resources/linux/code-url-handler.desktop @@ -2,7 +2,7 @@ Name=@@NAME_LONG@@ - URL Handler Comment=Azure Data Studio GenericName=Text Editor -Exec=@@EXEC@@ --open-url %U +Exec=@@EXEC@@ --no-sandbox --open-url %U Icon=@@ICON@@ Type=Application NoDisplay=true diff --git a/resources/linux/code.desktop b/resources/linux/code.desktop index 3940900ed6..53ad8dc607 100644 --- a/resources/linux/code.desktop +++ b/resources/linux/code.desktop @@ -2,7 +2,7 @@ Name=@@NAME_LONG@@ Comment=Data Management Tool that enables you to work with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux. GenericName=Text Editor -Exec=@@EXEC@@ --unity-launch %F +Exec=@@EXEC@@ --no-sandbox --unity-launch %F Icon=@@ICON@@ Type=Application StartupNotify=false @@ -14,5 +14,5 @@ Keywords=azuredatastudio; [Desktop Action new-empty-window] Name=New Empty Window -Exec=@@EXEC@@ --new-window %F +Exec=@@EXEC@@ --no-sandbox --new-window %F Icon=@@ICON@@ diff --git a/resources/win32/inno-big-125.bmp b/resources/win32/inno-big-125.bmp old mode 100755 new mode 100644 diff --git a/resources/win32/inno-big-150.bmp b/resources/win32/inno-big-150.bmp old mode 100755 new mode 100644 diff --git a/resources/win32/inno-big-175.bmp b/resources/win32/inno-big-175.bmp old mode 100755 new mode 100644 diff --git a/resources/win32/inno-big-200.bmp b/resources/win32/inno-big-200.bmp old mode 100755 new mode 100644 diff --git a/resources/win32/inno-big-225.bmp b/resources/win32/inno-big-225.bmp old mode 100755 new mode 100644 diff --git a/resources/win32/inno-small-100.bmp b/resources/win32/inno-small-100.bmp old mode 100755 new mode 100644 diff --git a/resources/win32/inno-small-125.bmp b/resources/win32/inno-small-125.bmp old mode 100755 new mode 100644 diff --git a/resources/win32/inno-small-150.bmp b/resources/win32/inno-small-150.bmp old mode 100755 new mode 100644 diff --git a/resources/win32/inno-small-175.bmp b/resources/win32/inno-small-175.bmp old mode 100755 new mode 100644 diff --git a/resources/win32/inno-small-200.bmp b/resources/win32/inno-small-200.bmp old mode 100755 new mode 100644 diff --git a/resources/win32/inno-small-225.bmp b/resources/win32/inno-small-225.bmp old mode 100755 new mode 100644 diff --git a/resources/win32/inno-small-250.bmp b/resources/win32/inno-small-250.bmp old mode 100755 new mode 100644 diff --git a/scripts/code-cli.bat b/scripts/code-cli.bat index 6fbf5edd62..56c3756720 100644 --- a/scripts/code-cli.bat +++ b/scripts/code-cli.bat @@ -24,7 +24,7 @@ if "%1"=="--builtin" goto builtin node build\lib\builtInExtensions.js :: Build -if not exist out node .\node_modules\gulp\bin\gulp.js compile +if not exist out yarn compile :: Configuration set ELECTRON_RUN_AS_NODE=1 diff --git a/scripts/code-cli.sh b/scripts/code-cli.sh index a7d61d5a89..e4fa552e64 100755 --- a/scripts/code-cli.sh +++ b/scripts/code-cli.sh @@ -22,8 +22,7 @@ function code() { test -d node_modules || yarn # Get electron - node build/lib/electron.js || ./node_modules/.bin/gulp electron - + yarn electron # Manage built-in extensions if [[ "$1" == "--builtin" ]]; then @@ -35,7 +34,7 @@ function code() { node build/lib/builtInExtensions.js # Build - test -d out || ./node_modules/.bin/gulp compile + test -d out || yarn compile ELECTRON_RUN_AS_NODE=1 \ NODE_ENV=development \ diff --git a/scripts/code-web.js b/scripts/code-web.js new file mode 100755 index 0000000000..33e586d958 --- /dev/null +++ b/scripts/code-web.js @@ -0,0 +1,225 @@ +#!/usr/bin/env node + +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// @ts-check + +const http = require('http'); +const url = require('url'); +const fs = require('fs'); +const path = require('path'); +const util = require('util'); +const opn = require('opn'); +const minimist = require('vscode-minimist'); + +const APP_ROOT = path.dirname(__dirname); +const WEB_MAIN = path.join(APP_ROOT, 'src', 'vs', 'code', 'browser', 'workbench', 'workbench-dev.html'); +const PORT = 8080; + +const args = minimist(process.argv, { + string: [ + 'no-launch' + ] +}); + +const server = http.createServer((req, res) => { + const parsedUrl = url.parse(req.url, true); + const pathname = parsedUrl.pathname; + + try { + if (pathname === '/favicon.ico') { + // favicon + return serveFile(req, res, path.join(APP_ROOT, 'resources', 'win32', 'code.ico')); + } + if (/^\/static\//.test(pathname)) { + // static requests + return handleStatic(req, res, parsedUrl); + } + if (/^\/static-extension\//.test(pathname)) { + // static extension requests + return handleStaticExtension(req, res, parsedUrl); + } + if (pathname === '/') { + // main web + return handleRoot(req, res); + } + + return serveError(req, res, 404, 'Not found.'); + } catch (error) { + console.error(error.toString()); + + return serveError(req, res, 500, 'Internal Server Error.'); + } +}); + +server.listen(PORT, () => { + console.log(`Web UI available at http://localhost:${PORT}`); +}); + +server.on('error', err => { + console.error(`Error occurred in server:`); + console.error(err); +}); + +/** + * @param {import('http').IncomingMessage} req + * @param {import('http').ServerResponse} res + * @param {import('url').UrlWithParsedQuery} parsedUrl + */ +function handleStatic(req, res, parsedUrl) { + + // Strip `/static/` from the path + const relativeFilePath = path.normalize(decodeURIComponent(parsedUrl.pathname.substr('/static/'.length))); + + return serveFile(req, res, path.join(APP_ROOT, relativeFilePath)); +} + +/** + * @param {import('http').IncomingMessage} req + * @param {import('http').ServerResponse} res + * @param {import('url').UrlWithParsedQuery} parsedUrl + */ +function handleStaticExtension(req, res, parsedUrl) { + + // Strip `/static-extension/` from the path + const relativeFilePath = path.normalize(decodeURIComponent(parsedUrl.pathname.substr('/static-extension/'.length))); + + const filePath = path.join(APP_ROOT, 'extensions', relativeFilePath); + + return serveFile(req, res, filePath); +} + +/** + * @param {import('http').IncomingMessage} req + * @param {import('http').ServerResponse} res + */ +async function handleRoot(req, res) { + const extensionFolders = await util.promisify(fs.readdir)(path.join(APP_ROOT, 'extensions')); + const mapExtensionFolderToExtensionPackageJSON = new Map(); + + await Promise.all(extensionFolders.map(async extensionFolder => { + try { + const packageJSON = JSON.parse((await util.promisify(fs.readFile)(path.join(APP_ROOT, 'extensions', extensionFolder, 'package.json'))).toString()); + if (packageJSON.main && packageJSON.name !== 'vscode-api-tests') { + return; // unsupported + } + + if (packageJSON.name === 'scss') { + return; // seems to fail to JSON.parse()?! + } + + packageJSON.extensionKind = 'web'; // enable for Web + + mapExtensionFolderToExtensionPackageJSON.set(extensionFolder, packageJSON); + } catch (error) { + return null; + } + })); + + const staticExtensions = []; + + // Built in extensions + mapExtensionFolderToExtensionPackageJSON.forEach((packageJSON, extensionFolder) => { + staticExtensions.push({ + packageJSON, + extensionLocation: { scheme: 'http', authority: `localhost:${PORT}`, path: `/static-extension/${extensionFolder}` } + }); + }); + + const data = (await util.promisify(fs.readFile)(WEB_MAIN)).toString() + .replace('{{WORKBENCH_WEB_CONFIGURATION}}', escapeAttribute(JSON.stringify({ + staticExtensions, + folderUri: { scheme: 'memfs', path: `/` } + }))) + .replace('{{WEBVIEW_ENDPOINT}}', '') + .replace('{{REMOTE_USER_DATA_URI}}', ''); + + res.writeHead(200, { 'Content-Type': 'text/html' }); + return res.end(data); +} + +/** + * @param {string} value + */ +function escapeAttribute(value) { + return value.replace(/"/g, '"'); +} + +/** + * @param {import('http').IncomingMessage} req + * @param {import('http').ServerResponse} res + * @param {string} errorMessage + */ +function serveError(req, res, errorCode, errorMessage) { + res.writeHead(errorCode, { 'Content-Type': 'text/plain' }); + res.end(errorMessage); +} + +const textMimeType = { + '.html': 'text/html', + '.js': 'text/javascript', + '.json': 'application/json', + '.css': 'text/css', + '.svg': 'image/svg+xml', +}; + +const mapExtToMediaMimes = { + '.bmp': 'image/bmp', + '.gif': 'image/gif', + '.ico': 'image/x-icon', + '.jpe': 'image/jpg', + '.jpeg': 'image/jpg', + '.jpg': 'image/jpg', + '.png': 'image/png', + '.tga': 'image/x-tga', + '.tif': 'image/tiff', + '.tiff': 'image/tiff', + '.woff': 'application/font-woff' +}; + +/** + * @param {string} forPath + */ +function getMediaMime(forPath) { + const ext = path.extname(forPath); + + return mapExtToMediaMimes[ext.toLowerCase()]; +} + +/** + * @param {import('http').IncomingMessage} req + * @param {import('http').ServerResponse} res + * @param {string} filePath + */ +async function serveFile(req, res, filePath, responseHeaders = Object.create(null)) { + try { + const stat = await util.promisify(fs.stat)(filePath); + + // Check if file modified since + const etag = `W/"${[stat.ino, stat.size, stat.mtime.getTime()].join('-')}"`; // weak validator (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) + if (req.headers['if-none-match'] === etag) { + res.writeHead(304); + return res.end(); + } + + // Headers + responseHeaders['Content-Type'] = textMimeType[path.extname(filePath)] || getMediaMime(filePath) || 'text/plain'; + responseHeaders['Etag'] = etag; + + res.writeHead(200, responseHeaders); + + // Data + fs.createReadStream(filePath).pipe(res); + } catch (error) { + console.error(error.toString()); + res.writeHead(404, { 'Content-Type': 'text/plain' }); + return res.end('Not found'); + } +} + +if (args.launch !== false) { + opn(`http://localhost:${PORT}`); +} diff --git a/scripts/code.bat b/scripts/code.bat index f4689608e4..0eb0eb0b34 100644 --- a/scripts/code.bat +++ b/scripts/code.bat @@ -24,7 +24,7 @@ if "%1"=="--builtin" goto builtin node build\lib\builtInExtensions.js :: Build -if not exist out node .\node_modules\gulp\bin\gulp.js compile +if not exist out yarn compile :: Configuration set NODE_ENV=development diff --git a/scripts/code.sh b/scripts/code.sh index e82babd818..4ba1a00b9f 100755 --- a/scripts/code.sh +++ b/scripts/code.sh @@ -27,7 +27,7 @@ function code() { test -d node_modules || yarn # Get electron - node build/lib/electron.js || ./node_modules/.bin/gulp electron + yarn electron # Manage built-in extensions if [[ "$1" == "--builtin" ]]; then @@ -39,7 +39,7 @@ function code() { node build/lib/builtInExtensions.js # Build - test -d out || ./node_modules/.bin/gulp compile + test -d out || yarn compile # Configuration export NODE_ENV=development diff --git a/scripts/node-electron.sh b/scripts/node-electron.sh index 7216e92a6c..1c3d894a1d 100644 --- a/scripts/node-electron.sh +++ b/scripts/node-electron.sh @@ -18,7 +18,7 @@ else fi # Get electron -node build/lib/electron.js || ./node_modules/.bin/gulp electron +yarn electron popd diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index 9a1e0b4565..a9732aec35 100755 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -30,22 +30,22 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( :: if %errorlevel% neq 0 exit /b %errorlevel% :: Tests in the extension host -call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testWorkspace --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\singlefolder-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --disable-inspect --user-data-dir=%VSCODEUSERDATADIR% + +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\singlefolder-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% if %errorlevel% neq 0 exit /b %errorlevel% -call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testworkspace.code-workspace --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\workspace-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --disable-inspect --user-data-dir=%VSCODEUSERDATADIR% +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\workspace-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% + if %errorlevel% neq 0 exit /b %errorlevel% -call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --disable-inspect --user-data-dir=%VSCODEUSERDATADIR% -call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\markdown-language-features\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\markdown-language-features --extensionTestsPath=%~dp0\..\extensions\markdown-language-features\out\test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --disable-inspect --user-data-dir=%VSCODEUSERDATADIR% -call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\azurecore\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\azurecore --extensionTestsPath=%~dp0\..\extensions\azurecore\out\test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --disable-inspect --user-data-dir=%VSCODEUSERDATADIR% +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% if %errorlevel% neq 0 exit /b %errorlevel% -:: call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --disable-inspect --user-data-dir=%VSCODEUSERDATADIR% +:: call "%INTEGRATION_TEST_ELECTRON_PATH%" $%~dp0\..\extensions\emmet\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% . :: if %errorlevel% neq 0 exit /b %errorlevel% -:: call "%INTEGRATION_TEST_ELECTRON_PATH%" $%~dp0\..\extensions\emmet\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --disable-inspect --user-data-dir=%VSCODEUSERDATADIR% . -:: if %errorlevel% neq 0 exit /b %errorlevel% +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\azurecore\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\azurecore --extensionTestsPath=%~dp0\..\extensions\azurecore\out\test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% +if %errorlevel% neq 0 exit /b %errorlevel% :: Tests in commonJS (HTML, CSS, JSON language server tests...) call .\scripts\node-electron.bat .\node_modules\mocha\bin\_mocha .\extensions\*\server\out\test\**\*.test.js diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index 6911d68021..424e656b68 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -8,7 +8,7 @@ if [[ "$OSTYPE" == "darwin"* ]]; then else ROOT=$(dirname $(dirname $(readlink -f $0))) VSCODEUSERDATADIR=`mktemp -d 2>/dev/null` - LINUX_NO_SANDBOX="" + LINUX_NO_SANDBOX="--no-sandbox" # Electron 6 introduces a chrome-sandbox that requires root to run. This can fail. Disable sandbox via --no-sandbox. fi cd $ROOT @@ -37,14 +37,15 @@ fi ./scripts/test.sh --runGlob **/*.integrationTest.js "$@" # Tests in the extension host -# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-api-tests/testWorkspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR -# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-api-tests/testworkspace.code-workspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/workspace-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR -"$INTEGRATION_TEST_ELECTRON_PATH" $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/markdown-language-features/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/azurecore/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/azurecore --extensionTestsPath=$ROOT/extensions/azurecore/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR +# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-api-tests/testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR +# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-api-tests/testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/workspace-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/markdown-language-features/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/azurecore/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/azurecore --extensionTestsPath=$ROOT/extensions/azurecore/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR + mkdir -p $ROOT/extensions/emmet/test-fixtures -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/emmet/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/emmet/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR rm -rf $ROOT/extensions/emmet/test-fixtures # Remote Integration Tests diff --git a/scripts/test.sh b/scripts/test.sh index 3ff498453f..ff5a943cfc 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -35,7 +35,7 @@ fi test -d node_modules || yarn # Get electron -node build/lib/electron.js || ./node_modules/.bin/gulp electron +yarn electron # Unit Tests if [[ "$OSTYPE" == "darwin"* ]] || [[ "$AGENT_OS" == "Darwin"* ]]; then @@ -47,5 +47,5 @@ else cd $ROOT ; \ ELECTRON_ENABLE_LOGGING=1 \ "$CODE" \ - test/electron/index.js $CODE_ARGS "$@" + test/electron/index.js --no-sandbox "$@" # Electron 6 introduces a chrome-sandbox that requires root to run. This can fail. Disable sandbox via --no-sandbox. fi diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index cad91629bd..f7b16145ea 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -21,7 +21,7 @@ exports.assign = function assign(destination, source) { * * @param {string[]} modulePaths * @param {(result, configuration: object) => any} resultCallback - * @param {{ forceEnableDeveloperKeybindings?: boolean, removeDeveloperKeybindingsAfterLoad?: boolean, canModifyDOM?: (config: object) => void, beforeLoaderConfig?: (config: object, loaderConfig: object) => void, beforeRequire?: () => void }=} options + * @param {{ forceEnableDeveloperKeybindings?: boolean, disallowReloadKeybinding?: boolean, removeDeveloperKeybindingsAfterLoad?: boolean, canModifyDOM?: (config: object) => void, beforeLoaderConfig?: (config: object, loaderConfig: object) => void, beforeRequire?: () => void }=} options */ exports.load = function (modulePaths, resultCallback, options) { @@ -58,7 +58,7 @@ exports.load = function (modulePaths, resultCallback, options) { const enableDeveloperTools = (process.env['VSCODE_DEV'] || !!configuration.extensionDevelopmentPath) && !configuration.extensionTestsPath; let developerToolsUnbind; if (enableDeveloperTools || (options && options.forceEnableDeveloperKeybindings)) { - developerToolsUnbind = registerDeveloperKeybindings(); + developerToolsUnbind = registerDeveloperKeybindings(options && options.disallowReloadKeybinding); } // Correctly inherit the parent's environment @@ -178,9 +178,10 @@ function parseURLQueryArgs() { } /** + * @param {boolean} disallowReloadKeybinding * @returns {() => void} */ -function registerDeveloperKeybindings() { +function registerDeveloperKeybindings(disallowReloadKeybinding) { // @ts-ignore const ipc = require('electron').ipcRenderer; @@ -204,7 +205,7 @@ function registerDeveloperKeybindings() { const key = extractKey(e); if (key === TOGGLE_DEV_TOOLS_KB || key === TOGGLE_DEV_TOOLS_KB_ALT) { ipc.send('vscode:toggleDevTools'); - } else if (key === RELOAD_KB) { + } else if (key === RELOAD_KB && !disallowReloadKeybinding) { ipc.send('vscode:reloadWindow'); } }; diff --git a/src/bootstrap.js b/src/bootstrap.js index 8d95ffee95..8a6a02ab5a 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -21,6 +21,7 @@ process.on('SIGPIPE', () => { //#endregion //#region Add support for redirecting the loading of node modules + exports.injectNodeModuleLookupPath = function (injectPath) { if (!injectPath) { throw new Error('Missing injectPath'); @@ -36,10 +37,8 @@ exports.injectNodeModuleLookupPath = function (injectPath) { const originalResolveLookupPaths = Module._resolveLookupPaths; // @ts-ignore - Module._resolveLookupPaths = function (moduleName, parent, newReturn) { - const result = originalResolveLookupPaths(moduleName, parent, newReturn); - - const paths = newReturn ? result : result[1]; + Module._resolveLookupPaths = function (moduleName, parent) { + const paths = originalResolveLookupPaths(moduleName, parent); for (let i = 0, len = paths.length; i < len; i++) { if (paths[i] === nodeModulesPath) { paths.splice(i, 0, injectPath); @@ -47,7 +46,7 @@ exports.injectNodeModuleLookupPath = function (injectPath) { } } - return result; + return paths; }; }; //#endregion @@ -71,11 +70,10 @@ exports.enableASARSupport = function (nodeModulesPath) { // @ts-ignore const originalResolveLookupPaths = Module._resolveLookupPaths; - // @ts-ignore - Module._resolveLookupPaths = function (request, parent, newReturn) { - const result = originalResolveLookupPaths(request, parent, newReturn); - const paths = newReturn ? result : result[1]; + // @ts-ignore + Module._resolveLookupPaths = function (request, parent) { + const paths = originalResolveLookupPaths(request, parent); for (let i = 0, len = paths.length; i < len; i++) { if (paths[i] === NODE_MODULES_PATH) { paths.splice(i, 0, NODE_MODULES_ASAR_PATH); @@ -83,7 +81,7 @@ exports.enableASARSupport = function (nodeModulesPath) { } } - return result; + return paths; }; }; //#endregion diff --git a/src/main.js b/src/main.js index c9d638dd83..d08f5de5cb 100644 --- a/src/main.js +++ b/src/main.js @@ -12,12 +12,14 @@ const lp = require('./vs/base/node/languagePacks'); perf.mark('main:started'); const path = require('path'); +const fs = require('fs'); +const os = require('os'); const bootstrap = require('./bootstrap'); const paths = require('./paths'); // @ts-ignore const product = require('../product.json'); // @ts-ignore -const app = require('electron').app; +const { app, protocol } = require('electron'); // Enable portable support const portable = bootstrap.configurePortable(); @@ -25,7 +27,7 @@ const portable = bootstrap.configurePortable(); // Enable ASAR support bootstrap.enableASARSupport(); -// Set userData path before app 'ready' event and call to process.chdir +// Set userData path before app 'ready' event const args = parseCLIArgs(); if (args['nogpu']) { // {{SQL CARBON EDIT}} @@ -37,32 +39,44 @@ if (args['nogpu']) { // {{SQL CARBON EDIT}} const userDataPath = getUserDataPath(args); app.setPath('userData', userDataPath); +// Set logs path before app 'ready' event if running portable +// to ensure that no 'logs' folder is created on disk at a +// location outside of the portable directory +// (https://github.com/microsoft/vscode/issues/56651) +if (portable.isPortable) { + app.setAppLogsPath(path.join(userDataPath, 'logs')); +} + // Update cwd based on environment and platform setCurrentWorkingDirectory(); +// Register custom schemes with privileges +protocol.registerSchemesAsPrivileged([ + { scheme: 'vscode-resource', privileges: { secure: true, supportFetchAPI: true, corsEnabled: true } } +]); + // Global app listeners registerListeners(); -/** - * Support user defined locale - * - * @type {Promise} - */ -let nlsConfiguration = undefined; -const userDefinedLocale = getUserDefinedLocale(); -const metaDataFile = path.join(__dirname, 'nls.metadata.json'); - -userDefinedLocale.then(locale => { - if (locale && !nlsConfiguration) { - nlsConfiguration = lp.getNLSConfiguration(product.commit, userDataPath, metaDataFile, locale); - } -}); - // Cached data const nodeCachedDataDir = getNodeCachedDir(); -// Configure command line switches -configureCommandlineSwitches(args); +// Configure static command line arguments +const argvConfig = configureCommandlineSwitchesSync(args); + +/** + * Support user defined locale: load it early before app('ready') + * to have more things running in parallel. + * + * @type {Promise} nlsConfig | undefined + */ +let nlsConfigurationPromise = undefined; + +const metaDataFile = path.join(__dirname, 'nls.metadata.json'); +const locale = getUserDefinedLocale(argvConfig); +if (locale) { + nlsConfigurationPromise = lp.getNLSConfiguration(product.commit, userDataPath, metaDataFile, locale); +} // Load our code once ready app.once('ready', function () { @@ -81,62 +95,35 @@ app.once('ready', function () { } }); -function onReady() { +/** + * Main startup routine + * + * @param {string | undefined} cachedDataDir + * @param {import('./vs/base/node/languagePacks').NLSConfiguration} nlsConfig + */ +function startup(cachedDataDir, nlsConfig) { + nlsConfig._languagePackSupport = true; + + process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig); + process.env['VSCODE_NODE_CACHED_DATA_DIR'] = cachedDataDir || ''; + + // Load main in AMD + perf.mark('willLoadMainBundle'); + require('./bootstrap-amd').load('vs/code/electron-main/main', () => { + perf.mark('didLoadMainBundle'); + }); +} + +async function onReady() { perf.mark('main:appReady'); - Promise.all([nodeCachedDataDir.ensureExists(), userDefinedLocale]).then(([cachedDataDir, locale]) => { - if (locale && !nlsConfiguration) { - nlsConfiguration = lp.getNLSConfiguration(product.commit, userDataPath, metaDataFile, locale); - } + try { + const [cachedDataDir, nlsConfig] = await Promise.all([nodeCachedDataDir.ensureExists(), resolveNlsConfiguration()]); - if (!nlsConfiguration) { - nlsConfiguration = Promise.resolve(undefined); - } - - // First, we need to test a user defined locale. If it fails we try the app locale. - // If that fails we fall back to English. - nlsConfiguration.then(nlsConfig => { - - const startup = nlsConfig => { - nlsConfig._languagePackSupport = true; - process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig); - process.env['VSCODE_NODE_CACHED_DATA_DIR'] = cachedDataDir || ''; - - // Load main in AMD - perf.mark('willLoadMainBundle'); - require('./bootstrap-amd').load('vs/code/electron-main/main', () => { - perf.mark('didLoadMainBundle'); - }); - }; - - // We received a valid nlsConfig from a user defined locale - if (nlsConfig) { - startup(nlsConfig); - } - - // Try to use the app locale. Please note that the app locale is only - // valid after we have received the app ready event. This is why the - // code is here. - else { - let appLocale = app.getLocale(); - if (!appLocale) { - startup({ locale: 'en', availableLanguages: {} }); - } else { - - // See above the comment about the loader and case sensitiviness - appLocale = appLocale.toLowerCase(); - - lp.getNLSConfiguration(product.commit, userDataPath, metaDataFile, appLocale).then(nlsConfig => { - if (!nlsConfig) { - nlsConfig = { locale: appLocale, availableLanguages: {} }; - } - - startup(nlsConfig); - }); - } - } - }); - }, console.error); + startup(cachedDataDir, nlsConfig); + } catch (error) { + console.error(error); + } } /** @@ -144,16 +131,147 @@ function onReady() { * * @param {ParsedArgs} cliArgs */ -function configureCommandlineSwitches(cliArgs) { +function configureCommandlineSwitchesSync(cliArgs) { + const SUPPORTED_ELECTRON_SWITCHES = [ - // Force pre-Chrome-60 color profile handling (for https://github.com/Microsoft/vscode/issues/51791) - app.commandLine.appendSwitch('disable-color-correct-rendering'); + // alias from us for --disable-gpu + 'disable-hardware-acceleration', + + // provided by Electron + 'disable-color-correct-rendering' + ]; + + // Read argv config + const argvConfig = readArgvConfigSync(); + + // Append each flag to Electron + Object.keys(argvConfig).forEach(argvKey => { + if (SUPPORTED_ELECTRON_SWITCHES.indexOf(argvKey) === -1) { + return; // unsupported argv key + } + + const argvValue = argvConfig[argvKey]; + if (argvValue === true || argvValue === 'true') { + if (argvKey === 'disable-hardware-acceleration') { + app.disableHardwareAcceleration(); // needs to be called explicitly + } else { + app.commandLine.appendArgument(argvKey); + } + } else { + app.commandLine.appendSwitch(argvKey, argvValue); + } + }); // Support JS Flags const jsFlags = getJSFlags(cliArgs); if (jsFlags) { - app.commandLine.appendSwitch('--js-flags', jsFlags); + app.commandLine.appendSwitch('js-flags', jsFlags); } + + return argvConfig; +} + +function readArgvConfigSync() { + + // Read or create the argv.json config file sync before app('ready') + const argvConfigPath = getArgvConfigPath(); + let argvConfig; + try { + argvConfig = JSON.parse(stripComments(fs.readFileSync(argvConfigPath).toString())); + } catch (error) { + if (error && error.code === 'ENOENT') { + createDefaultArgvConfigSync(argvConfigPath); + } else { + console.warn(`Unable to read argv.json configuration file in ${argvConfigPath}, falling back to defaults (${error})`); + } + } + + // Fallback to default + if (!argvConfig) { + argvConfig = { + 'disable-color-correct-rendering': true // Force pre-Chrome-60 color profile handling (for https://github.com/Microsoft/vscode/issues/51791) + }; + } + + return argvConfig; +} + +/** + * @param {string} argvConfigPath + */ +function createDefaultArgvConfigSync(argvConfigPath) { + try { + + // Ensure argv config parent exists + const argvConfigPathDirname = path.dirname(argvConfigPath); + if (!fs.existsSync(argvConfigPathDirname)) { + fs.mkdirSync(argvConfigPathDirname); + } + + // Migrate over legacy locale + const localeConfigPath = path.join(userDataPath, 'User', 'locale.json'); + const legacyLocale = getLegacyUserDefinedLocaleSync(localeConfigPath); + if (legacyLocale) { + try { + fs.unlinkSync(localeConfigPath); + } catch (error) { + //ignore + } + } + + // Default argv content + const defaultArgvConfigContent = [ + '// This configuration file allows to pass permanent command line arguments to VSCode.', + '// Only a subset of arguments is currently supported to reduce the likelyhood of breaking', + '// the installation.', + '//', + '// PLEASE DO NOT CHANGE WITHOUT UNDERSTANDING THE IMPACT', + '//', + '// If the command line argument does not have any values, simply assign', + '// it in the JSON below with a value of \'true\'. Otherwise, put the value', + '// directly.', + '//', + '// If you see rendering issues in VSCode and have a better experience', + '// with software rendering, you can configure this by adding:', + '//', + '// \'disable-hardware-acceleration\': true', + '//', + '// NOTE: Changing this file requires a restart of VSCode.', + '{', + ' // Enabled by default by VSCode to resolve color issues in the renderer', + ' // See https://github.com/Microsoft/vscode/issues/51791 for details', + ' "disable-color-correct-rendering": true' + ]; + + if (legacyLocale) { + defaultArgvConfigContent[defaultArgvConfigContent.length - 1] = `${defaultArgvConfigContent[defaultArgvConfigContent.length - 1]},`; // append trailing "," + + defaultArgvConfigContent.push(''); + defaultArgvConfigContent.push(' // Display language of VSCode'); + defaultArgvConfigContent.push(` "locale": "${legacyLocale}"`); + } + + defaultArgvConfigContent.push('}'); + + // Create initial argv.json with default content + fs.writeFileSync(argvConfigPath, defaultArgvConfigContent.join('\n')); + } catch (error) { + console.error(`Unable to create argv.json configuration file in ${argvConfigPath}, falling back to defaults (${error})`); + } +} + +function getArgvConfigPath() { + const vscodePortable = process.env['VSCODE_PORTABLE']; + if (vscodePortable) { + return path.join(vscodePortable, 'argv.json'); + } + + let dataFolderName = product.dataFolderName; + if (process.env['VSCODE_DEV']) { + dataFolderName = `${dataFolderName}-dev`; + } + + return path.join(os.homedir(), dataFolderName, 'argv.json'); } /** @@ -256,7 +374,7 @@ function registerListeners() { } /** - * @returns {{ ensureExists: () => Promise }} + * @returns {{ ensureExists: () => Promise }} */ function getNodeCachedDir() { return new class { @@ -265,8 +383,14 @@ function getNodeCachedDir() { this.value = this._compute(); } - ensureExists() { - return bootstrap.mkdirp(this.value).then(() => this.value, () => { /*ignore*/ }); + async ensureExists() { + try { + await bootstrap.mkdirp(this.value); + + return this.value; + } catch (error) { + // ignore + } } _compute() { @@ -291,6 +415,41 @@ function getNodeCachedDir() { } //#region NLS Support +/** + * Resolve the NLS configuration + * + * @return {Promise} + */ +async function resolveNlsConfiguration() { + + // First, we need to test a user defined locale. If it fails we try the app locale. + // If that fails we fall back to English. + let nlsConfiguration = nlsConfigurationPromise ? await nlsConfigurationPromise : undefined; + if (!nlsConfiguration) { + + // Try to use the app locale. Please note that the app locale is only + // valid after we have received the app ready event. This is why the + // code is here. + let appLocale = app.getLocale(); + if (!appLocale) { + nlsConfiguration = { locale: 'en', availableLanguages: {} }; + } else { + + // See above the comment about the loader and case sensitiviness + appLocale = appLocale.toLowerCase(); + + nlsConfiguration = await lp.getNLSConfiguration(product.commit, userDataPath, metaDataFile, appLocale); + if (!nlsConfiguration) { + nlsConfiguration = { locale: appLocale, availableLanguages: {} }; + } + } + } else { + // We received a valid nlsConfig from a user defined locale + } + + return nlsConfiguration; +} + /** * @param {string} content * @returns {string} @@ -319,30 +478,36 @@ function stripComments(content) { }); } -// Language tags are case insensitive however an amd loader is case sensitive -// To make this work on case preserving & insensitive FS we do the following: -// the language bundles have lower case language tags and we always lower case -// the locale we receive from the user or OS. /** - * @returns {Promise} + * Language tags are case insensitive however an amd loader is case sensitive + * To make this work on case preserving & insensitive FS we do the following: + * the language bundles have lower case language tags and we always lower case + * the locale we receive from the user or OS. + * + * @param {{ locale: string | undefined; }} argvConfig + * @returns {string | undefined} */ -function getUserDefinedLocale() { +function getUserDefinedLocale(argvConfig) { const locale = args['locale']; if (locale) { - return Promise.resolve(locale.toLowerCase()); + return locale.toLowerCase(); // a directly provided --locale always wins } - const localeConfig = path.join(userDataPath, 'User', 'locale.json'); - return bootstrap.readFile(localeConfig).then(content => { - content = stripComments(content); - try { - const value = JSON.parse(content).locale; - return value && typeof value === 'string' ? value.toLowerCase() : undefined; - } catch (e) { - return undefined; - } - }, () => { - return undefined; - }); + return argvConfig.locale && typeof argvConfig.locale === 'string' ? argvConfig.locale.toLowerCase() : undefined; +} + +/** + * @param {string} localeConfigPath + * @returns {string | undefined} + */ +function getLegacyUserDefinedLocaleSync(localeConfigPath) { + try { + const content = stripComments(fs.readFileSync(localeConfigPath).toString()); + + const value = JSON.parse(content).locale; + return value && typeof value === 'string' ? value.toLowerCase() : undefined; + } catch (error) { + // ignore + } } //#endregion diff --git a/src/sql/base/browser/ui/dropdownList/dropdownList.ts b/src/sql/base/browser/ui/dropdownList/dropdownList.ts index 534e3b5842..d5f2634aa9 100644 --- a/src/sql/base/browser/ui/dropdownList/dropdownList.ts +++ b/src/sql/base/browser/ui/dropdownList/dropdownList.ts @@ -131,22 +131,22 @@ export class DropdownList extends Dropdown { } protected applyStyles(): void { - const background = this.backgroundColor ? this.backgroundColor.toString() : null; - const foreground = this.foregroundColor ? this.foregroundColor.toString() : null; - const border = this.borderColor ? this.borderColor.toString() : null; + const background = this.backgroundColor ? this.backgroundColor.toString() : ''; + const foreground = this.foregroundColor ? this.foregroundColor.toString() : ''; + const border = this.borderColor ? this.borderColor.toString() : ''; this.applyStylesOnElement(this._contentContainer, background, foreground, border); if (this.label) { this.applyStylesOnElement(this.element, background, foreground, border); } } - private applyStylesOnElement(element: HTMLElement, background: string | null, foreground: string | null, border: string | null): void { + private applyStylesOnElement(element: HTMLElement, background: string, foreground: string, border: string): void { if (element) { element.style.backgroundColor = background; element.style.color = foreground; - element.style.borderWidth = border ? '1px' : null; - element.style.borderStyle = border ? 'solid' : null; + element.style.borderWidth = border ? '1px' : ''; + element.style.borderStyle = border ? 'solid' : ''; element.style.borderColor = border; } } diff --git a/src/sql/base/browser/ui/listBox/listBox.ts b/src/sql/base/browser/ui/listBox/listBox.ts index 7e22fa50c7..bde535d157 100644 --- a/src/sql/base/browser/ui/listBox/listBox.ts +++ b/src/sql/base/browser/ui/listBox/listBox.ts @@ -120,7 +120,7 @@ export class ListBox extends SelectBox { this.selectElement.style.border = `1px solid ${this.selectBorder}`; } else if (this.message) { const styles = this.stylesForType(this.message.type); - this.selectElement.style.border = styles.border ? `1px solid ${styles.border}` : null; + this.selectElement.style.border = styles.border ? `1px solid ${styles.border}` : ''; } } @@ -224,8 +224,8 @@ export class ListBox extends SelectBox { dom.addClass(spanElement, this.classForType(this.message.type)); const styles = this.stylesForType(this.message.type); - spanElement.style.backgroundColor = styles.background ? styles.background.toString() : null; - spanElement.style.border = styles.border ? `1px solid ${styles.border}` : null; + spanElement.style.backgroundColor = styles.background ? styles.background.toString() : ''; + spanElement.style.border = styles.border ? `1px solid ${styles.border}` : ''; dom.append(div, spanElement); } diff --git a/src/sql/base/browser/ui/selectBox/selectBox.ts b/src/sql/base/browser/ui/selectBox/selectBox.ts index cab475b046..4fdbda84fa 100644 --- a/src/sql/base/browser/ui/selectBox/selectBox.ts +++ b/src/sql/base/browser/ui/selectBox/selectBox.ts @@ -224,8 +224,8 @@ export class SelectBox extends vsSelectBox { dom.addClass(spanElement, this.classForType(message.type)); const styles = this.stylesForType(message.type); - spanElement.style.backgroundColor = styles.background ? styles.background.toString() : null; - spanElement.style.border = styles.border ? `1px solid ${styles.border}` : null; + spanElement.style.backgroundColor = styles.background ? styles.background.toString() : ''; + spanElement.style.border = styles.border ? `1px solid ${styles.border}` : ''; dom.append(div, spanElement); diff --git a/src/sql/base/parts/editableDropdown/browser/dropdown.ts b/src/sql/base/parts/editableDropdown/browser/dropdown.ts index 08e6a2ebfd..5ada9b3cd1 100644 --- a/src/sql/base/parts/editableDropdown/browser/dropdown.ts +++ b/src/sql/base/parts/editableDropdown/browser/dropdown.ts @@ -227,7 +227,7 @@ export class Dropdown extends Disposable { } private _showList(): void { - if (this._input.isEnabled) { + if (this._input.isEnabled()) { this._onFocus.fire(); this._filter.filterString = ''; this.contextViewService.showContextView({ @@ -297,7 +297,7 @@ export class Dropdown extends Disposable { style(style: IListStyles & IInputBoxStyles & IDropdownStyles) { this._tree.style(style); this._input.style(style); - this._treeContainer.style.backgroundColor = style.contextBackground ? style.contextBackground.toString() : null; + this._treeContainer.style.backgroundColor = style.contextBackground ? style.contextBackground.toString() : ''; this._treeContainer.style.outline = `1px solid ${style.contextBorder || this._options.contextBorder}`; } diff --git a/src/sql/workbench/browser/modelComponents/formContainer.component.ts b/src/sql/workbench/browser/modelComponents/formContainer.component.ts index 631fb47bf7..1f423b9d57 100644 --- a/src/sql/workbench/browser/modelComponents/formContainer.component.ts +++ b/src/sql/workbench/browser/modelComponents/formContainer.component.ts @@ -29,10 +29,6 @@ export interface TitledFormItemLayout { isGroupLabel?: boolean; } -export interface FormLayout { - width: number; -} - class FormItem { constructor(public descriptor: IComponentDescriptor, public config: TitledFormItemLayout) { } } diff --git a/src/sql/workbench/browser/modelComponents/webview.component.ts b/src/sql/workbench/browser/modelComponents/webview.component.ts index 56443d9128..984cf54b00 100644 --- a/src/sql/workbench/browser/modelComponents/webview.component.ts +++ b/src/sql/workbench/browser/modelComponents/webview.component.ts @@ -108,7 +108,6 @@ export default class WebViewComponent extends ComponentBase implements IComponen if (this._webview && this.html) { this._renderedHtml = this.html; this._webview.html = this._renderedHtml; - this._webview.layout(); } } @@ -142,7 +141,6 @@ export default class WebViewComponent extends ComponentBase implements IComponen this._ready.then(() => { let element = this._el.nativeElement; element.style.position = this.position; - this._webview.layout(); }); } } diff --git a/src/sql/workbench/parts/dashboard/browser/contents/webviewContent.component.ts b/src/sql/workbench/parts/dashboard/browser/contents/webviewContent.component.ts index 15935e239e..d11658e927 100644 --- a/src/sql/workbench/parts/dashboard/browser/contents/webviewContent.component.ts +++ b/src/sql/workbench/parts/dashboard/browser/contents/webviewContent.component.ts @@ -53,7 +53,7 @@ export class WebviewContent extends AngularDisposable implements OnInit, IDashbo } public layout(): void { - this._webview.layout(); + // no op } public get id(): string { @@ -80,7 +80,6 @@ export class WebviewContent extends AngularDisposable implements OnInit, IDashbo this._html = html; if (this._webview) { this._webview.html = html; - this._webview.layout(); } } @@ -113,6 +112,5 @@ export class WebviewContent extends AngularDisposable implements OnInit, IDashbo if (this._html) { this._webview.html = this._html; } - this._webview.layout(); } } diff --git a/src/sql/workbench/parts/dashboard/browser/widgets/webview/webviewWidget.component.ts b/src/sql/workbench/parts/dashboard/browser/widgets/webview/webviewWidget.component.ts index 572db5cdbf..e19f273086 100644 --- a/src/sql/workbench/parts/dashboard/browser/widgets/webview/webviewWidget.component.ts +++ b/src/sql/workbench/parts/dashboard/browser/widgets/webview/webviewWidget.component.ts @@ -60,7 +60,6 @@ export class WebviewWidget extends DashboardWidget implements IDashboardWidget, this._html = html; if (this._webview) { this._webview.html = html; - this._webview.layout(); } } @@ -81,7 +80,7 @@ export class WebviewWidget extends DashboardWidget implements IDashboardWidget, } public layout(): void { - this._webview.layout(); + // no op } public sendMessage(message: string): void { @@ -111,6 +110,5 @@ export class WebviewWidget extends DashboardWidget implements IDashboardWidget, if (this._html) { this._webview.html = this._html; } - this._webview.layout(); } } diff --git a/src/sql/workbench/parts/query/browser/queryResultsEditor.ts b/src/sql/workbench/parts/query/browser/queryResultsEditor.ts index 0ee8825fcd..8bce163063 100644 --- a/src/sql/workbench/parts/query/browser/queryResultsEditor.ts +++ b/src/sql/workbench/parts/query/browser/queryResultsEditor.ts @@ -43,14 +43,7 @@ export class BareResultsGridInfo extends BareFontInfo { protected constructor(fontInfo: BareFontInfo, opts: { cellPadding: number | number[]; }) { - super({ - zoomLevel: fontInfo.zoomLevel, - fontFamily: fontInfo.fontFamily, - fontWeight: fontInfo.fontWeight, - fontSize: fontInfo.fontSize, - lineHeight: fontInfo.lineHeight, - letterSpacing: fontInfo.letterSpacing - }); + super(fontInfo); this.cellPadding = opts.cellPadding; } } diff --git a/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts b/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts index 2cb4cb3a9c..86a324ebde 100644 --- a/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts +++ b/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts @@ -25,6 +25,7 @@ import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; class TestEnvironmentService implements IWorkbenchEnvironmentService { + argvResource: URI; userDataSyncLogResource: URI; settingsSyncPreviewResource: URI; webviewExternalEndpoint: string; diff --git a/src/typings/electron.d.ts b/src/typings/electron.d.ts index 6f44785756..f1d41613b5 100644 --- a/src/typings/electron.d.ts +++ b/src/typings/electron.d.ts @@ -1,4 +1,4 @@ -// Type definitions for Electron 4.2.10 +// Type definitions for Electron 6.0.12 // Project: http://electronjs.org/ // Definitions by: The Electron Team // Definitions: https://github.com/electron/electron-typescript-definitions @@ -8,6 +8,7 @@ type GlobalEvent = Event; declare namespace Electron { + // TODO: Replace this declaration with NodeJS.EventEmitter class EventEmitter { addListener(event: string, listener: Function): this; on(event: string, listener: Function): this; @@ -21,28 +22,17 @@ declare namespace Electron { listenerCount(type: string): number; prependListener(event: string, listener: Function): this; prependOnceListener(event: string, listener: Function): this; - eventNames(): string[]; + eventNames(): Array<(string | symbol)>; } class Accelerator extends String { } - interface Event extends GlobalEvent { - preventDefault: () => void; - sender: WebContents; - returnValue: any; - ctrlKey?: boolean; - metaKey?: boolean; - shiftKey?: boolean; - altKey?: boolean; - } - interface CommonInterface { clipboard: Clipboard; crashReporter: CrashReporter; nativeImage: typeof NativeImage; - screen: Screen; shell: Shell; } @@ -69,6 +59,7 @@ declare namespace Electron { powerMonitor: PowerMonitor; powerSaveBlocker: PowerSaveBlocker; protocol: Protocol; + screen: Screen; session: typeof Session; systemPreferences: SystemPreferences; TouchBar: typeof TouchBar; @@ -203,9 +194,9 @@ declare namespace Electron { userInfo: any) => void): this; /** * Emitted before the application starts closing its windows. Calling - * event.preventDefault() will prevent the default behaviour, which is terminating + * event.preventDefault() will prevent the default behavior, which is terminating * the application. Note: If application quit was initiated by - * autoUpdater.quitAndInstall() then before-quit is emitted after emitting close + * autoUpdater.quitAndInstall(), then before-quit is emitted after emitting close * event on all windows and closing them. Note: On Windows, this event will not be * emitted if the app is closed due to a shutdown/restart of the system or a user * logout. @@ -372,6 +363,18 @@ declare namespace Electron { * A string with the error's localized description. */ error: string) => void): this; + /** + * Emitted when desktopCapturer.getSources() is called in the renderer process of + * webContents. Calling event.preventDefault() will make it return empty sources. + */ + on(event: 'desktop-capturer-get-sources', listener: (event: Event, + webContents: WebContents) => void): this; + once(event: 'desktop-capturer-get-sources', listener: (event: Event, + webContents: WebContents) => void): this; + addListener(event: 'desktop-capturer-get-sources', listener: (event: Event, + webContents: WebContents) => void): this; + removeListener(event: 'desktop-capturer-get-sources', listener: (event: Event, + webContents: WebContents) => void): this; /** * Emitted when the gpu process crashes or is killed. */ @@ -385,7 +388,7 @@ declare namespace Electron { killed: boolean) => void): this; /** * Emitted when webContents wants to do basic auth. The default behavior is to - * cancel all authentications, to override this you should prevent the default + * cancel all authentications. To override this you should prevent the default * behavior with event.preventDefault() and call callback(username, password) with * the credentials. */ @@ -566,13 +569,30 @@ declare namespace Electron { removeListener(event: 'remote-require', listener: (event: Event, webContents: WebContents, moduleName: string) => void): this; + /** + * Emitted when the renderer process of webContents crashes or is killed. + */ + on(event: 'renderer-process-crashed', listener: (event: Event, + webContents: WebContents, + killed: boolean) => void): this; + once(event: 'renderer-process-crashed', listener: (event: Event, + webContents: WebContents, + killed: boolean) => void): this; + addListener(event: 'renderer-process-crashed', listener: (event: Event, + webContents: WebContents, + killed: boolean) => void): this; + removeListener(event: 'renderer-process-crashed', listener: (event: Event, + webContents: WebContents, + killed: boolean) => 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 - * command line arguments, and workingDirectory is its current working directory. - * Usually applications respond to this by making their primary window focused and - * non-minimized. This event is guaranteed to be emitted after the ready event of - * app gets emitted. + * a second instance has been executed and calls app.requestSingleInstanceLock(). + * argv is an Array of the second instance's command line arguments, and + * workingDirectory is its current working directory. Usually applications respond + * to this by making their primary window focused and non-minimized. This event is + * guaranteed to be emitted after the ready event of app gets emitted. Note: Extra + * command line arguments might be added by Chromium, such as + * --original-process-start-time. */ on(event: 'second-instance', listener: (event: Event, /** @@ -647,8 +667,8 @@ declare namespace Electron { * Emitted when Handoff is about to be resumed on another device. If you need to * update the state to be transferred, you should call event.preventDefault() * immediately, construct a new userInfo dictionary and call - * app.updateCurrentActiviy() in a timely manner. Otherwise the operation will fail - * and continue-activity-error will be called. + * app.updateCurrentActiviy() in a timely manner. Otherwise, the operation will + * fail and continue-activity-error will be called. */ on(event: 'update-activity-state', listener: (event: Event, /** @@ -760,8 +780,8 @@ declare namespace Electron { removeListener(event: 'window-all-closed', listener: Function): this; /** * Adds path to the recent documents list. This list is managed by the OS. On - * Windows you can visit the list from the task bar, and on macOS you can visit it - * from dock menu. + * Windows, you can visit the list from the task bar, and on macOS, you can visit + * it from dock menu. */ addRecentDocument(path: string): void; /** @@ -779,11 +799,6 @@ declare namespace Electron { * before app is ready. */ disableHardwareAcceleration(): void; - /** - * Enables mixed sandbox mode on the app. This method can only be called before app - * is ready. - */ - enableMixedSandbox(): void; /** * Enables full sandbox mode on the app. This method can only be called before app * is ready. @@ -791,8 +806,8 @@ declare namespace Electron { 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 - * will not be emitted. + * closed immediately without asking the user, and the before-quit and will-quit + * events will not be emitted. */ exit(exitCode?: number): void; /** @@ -808,12 +823,19 @@ 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, callback: (error: Error, icon: NativeImage) => void): void; + getFileIcon(path: string, options?: FileIconOptions): Promise; /** - * 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. + * Fetches a path's associated icon. On Windows, there are 2 kinds of icons: On + * Linux and macOS, icons depend on the application associated with file mime type. + * Deprecated Soon */ getFileIcon(path: string, options: FileIconOptions, callback: (error: Error, icon: NativeImage) => void): void; + /** + * Fetches a path's associated icon. On Windows, there are 2 kinds of icons: On + * Linux and macOS, icons depend on the application associated with file mime type. + * Deprecated Soon + */ + getFileIcon(path: string, callback: (error: Error, icon: NativeImage) => void): void; getGPUFeatureStatus(): GPUFeatureStatus; /** * For infoType equal to complete: Promise is fulfilled with Object containing all @@ -828,12 +850,16 @@ declare namespace Electron { /** * To set the locale, you'll want to use a command line switch at app startup, * which may be found here. Note: When distributing your packaged app, you have to - * also ship the locales folder. Note: On Windows you have to call it after the + * also ship the locales folder. Note: On Windows, you have to call it after the * ready events gets emitted. */ getLocale(): string; /** - * If you provided path and args options to app.setLoginItemSettings then you need + * Note: When unable to detect locale country code, it returns empty string. + */ + getLocaleCountryCode(): string; + /** + * If you provided path and args options to app.setLoginItemSettings, then you need * to pass the same arguments here for openAtLogin to be set correctly. */ getLoginItemSettings(options?: LoginItemSettingsOptions): LoginItemSettings; @@ -870,6 +896,9 @@ declare namespace Electron { * Invalidates the current Handoff user activity. */ invalidateCurrentActivity(type: string): void; + /** + * Deprecated Soon + */ isAccessibilitySupportEnabled(): boolean; /** * This method checks if the current executable is the default handler for a @@ -881,15 +910,16 @@ declare namespace Electron { * the Windows Registry and LSCopyDefaultHandlerForURLScheme internally. */ isDefaultProtocolClient(protocol: string, path?: string, args?: string[]): boolean; + isEmojiPanelSupported(): boolean; isInApplicationsFolder(): boolean; isReady(): boolean; isUnityRunning(): boolean; /** - * No confirmation dialog will be presented by default, if you wish to allow the - * user to confirm the operation you may do so using the dialog API. NOTE: This + * No confirmation dialog will be presented by default. If you wish to allow the + * user to confirm the operation, you may do so using the dialog API. NOTE: This * method throws errors if anything other than the user causes the move to fail. - * For instance if the user cancels the authorization dialog this method returns - * false. If we fail to perform the copy then this method will throw an error. The + * For instance if the user cancels the authorization dialog, this method returns + * false. If we fail to perform the copy, then this method will throw an error. The * message in the error should be informative and tell you exactly what went wrong */ moveToApplicationsFolder(): boolean; @@ -903,14 +933,14 @@ declare namespace Electron { */ quit(): void; /** - * Relaunches the app when current instance exits. By default the new instance will - * use the same working directory and command line arguments with current instance. - * When args is specified, the args will be passed as command line arguments - * instead. When execPath is specified, the execPath will be executed for relaunch - * instead of current app. Note that this method does not quit the app when - * executed, you have to call app.quit or app.exit after calling app.relaunch to - * make the app restart. When app.relaunch is called for multiple times, multiple - * instances will be started after current instance exited. An example of + * Relaunches the app when current instance exits. By default, the new instance + * will use the same working directory and command line arguments with current + * instance. When args is specified, the args will be passed as command line + * arguments instead. When execPath is specified, the execPath will be executed for + * relaunch instead of current app. Note that this method does not quit the app + * when executed, you have to call app.quit or app.exit after calling app.relaunch + * to make the app restart. When app.relaunch is called for multiple times, + * multiple instances will be started after current instance exited. An example of * restarting current instance immediately and adding a new command line argument * to the new instance: */ @@ -926,27 +956,25 @@ declare namespace Electron { */ removeAsDefaultProtocolClient(protocol: string, path?: string, args?: string[]): boolean; /** - * This method makes your application a Single Instance Application - instead of - * allowing multiple instances of your app to run, this will ensure that only a - * single instance of your app is running, and other instances signal this instance - * and exit. The return value of this method indicates whether or not this instance - * of your application successfully obtained the lock. If it failed to obtain the - * lock you can assume that another instance of your application is already running - * with the lock and exit immediately. I.e. This method returns true if your - * process is the primary instance of your application and your app should continue - * loading. It returns false if your process should immediately quit as it has - * sent its parameters to another instance that has already acquired the lock. On - * macOS the system enforces single instance automatically when users try to open a - * second instance of your app in Finder, and the open-file and open-url events - * will be emitted for that. However when users start your app in command line the - * system's single instance mechanism will be bypassed and you have to use this + * The return value of this method indicates whether or not this instance of your + * application successfully obtained the lock. If it failed to obtain the lock, + * you can assume that another instance of your application is already running with + * the lock and exit immediately. I.e. This method returns true if your process is + * the primary instance of your application and your app should continue loading. + * It returns false if your process should immediately quit as it has sent its + * parameters to another instance that has already acquired the lock. On macOS, the + * system enforces single instance automatically when users try to open a second + * instance of your app in Finder, and the open-file and open-url events will be + * emitted for that. However when users start your app in command line, the + * system's single instance mechanism will be bypassed, and you have to use this * method to ensure single instance. An example of activating the window of primary * instance when a second instance starts: */ requestSingleInstanceLock(): boolean; /** * Set the about panel options. This will override the values defined in the app's - * .plist file. See the Apple docs for more details. + * .plist file on MacOS. See the Apple docs for more details. On Linux, values must + * be set in order to be shown; there are no defaults. */ setAboutPanelOptions(options: AboutPanelOptionsOptions): void; /** @@ -955,9 +983,17 @@ declare namespace Electron { * 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. + * default. Deprecated Soon */ setAccessibilitySupportEnabled(enabled: boolean): void; + /** + * Sets or creates a directory your app's logs which can then be manipulated with + * app.getPath() or app.setPath(pathName, newPath). Calling app.setAppLogsPath() + * without a path parameter will result in this directory being set to + * /Library/Logs/YourAppName on macOS, and inside the userData directory on Linux + * and Windows. + */ + setAppLogsPath(path?: string): void; /** * Changes the Application User Model ID to id. */ @@ -967,20 +1003,23 @@ declare namespace Electron { * (aka URI scheme). It allows you to integrate your app deeper into the operating * system. Once registered, all links with your-protocol:// will be opened with the * current executable. The whole link, including protocol, will be passed to your - * application as a parameter. On Windows you can provide optional parameters path, - * the path to your executable, and args, an array of arguments to be passed to - * your executable when it launches. Note: On macOS, you can only register + * application as a parameter. On Windows, you can provide optional parameters + * path, the path to your executable, and args, an array of arguments to be passed + * to your executable when it launches. Note: On macOS, you can only register * protocols that have been added to your app's info.plist, which can not be * modified at runtime. You can however change the file with a simple text editor * or script during build time. Please refer to Apple's documentation for details. - * The API uses the Windows Registry and LSSetDefaultHandlerForURLScheme - * internally. + * Note: In a Windows Store environment (when packaged as an appx) this API will + * return true for all calls but the registry key it sets won't be accessible by + * other applications. In order to register your Windows Store application as a + * default protocol handler you must declare the protocol in your manifest. The API + * uses the Windows Registry and LSSetDefaultHandlerForURLScheme internally. */ setAsDefaultProtocolClient(protocol: string, path?: string, args?: string[]): boolean; /** * Sets the counter badge for current app. Setting the count to 0 will hide the - * badge. On macOS it shows on the dock icon. On Linux it only works for Unity - * launcher, Note: Unity launcher requires the existence of a .desktop file to + * badge. On macOS, it shows on the dock icon. On Linux, it only works for Unity + * launcher. Note: Unity launcher requires the existence of a .desktop file to * work, for more information please read Desktop Environment Integration. */ setBadgeCount(count: number): boolean; @@ -1012,12 +1051,12 @@ declare namespace Electron { setName(name: string): void; /** * Overrides the path to a special directory or file associated with name. If the - * path specifies a directory that does not exist, the directory will be created by - * this method. On failure an Error is thrown. You can only override paths of a - * name defined in app.getPath. By default, web pages' cookies and caches will be - * stored under the userData directory. If you want to change this location, you - * have to override the userData path before the ready event of the app module is - * emitted. + * path specifies a directory that does not exist, an Error is thrown. In that + * case, the directory should be created with fs.mkdirSync or similar. You can only + * override paths of a name defined in app.getPath. By default, web pages' cookies + * and caches will be stored under the userData directory. If you want to change + * this location, you have to override the userData path before the ready event of + * the app module is emitted. */ setPath(name: string, path: string): void; /** @@ -1037,10 +1076,14 @@ declare namespace Electron { */ show(): void; /** - * Show the about panel with the values defined in the app's .plist file or with - * the options set via app.setAboutPanelOptions(options). + * Show the app's about panel options. These options can be overridden with + * app.setAboutPanelOptions(options). */ showAboutPanel(): void; + /** + * Show the platform's native emoji picker. + */ + showEmojiPanel(): void; /** * Start accessing a security scoped resource. With this method Electron * applications that are packaged for the Mac App Store may reach outside their @@ -1054,6 +1097,34 @@ declare namespace Electron { */ updateCurrentActivity(type: string, userInfo: any): void; whenReady(): Promise; + /** + * A Boolean property that's true if Chrome's accessibility support is enabled, + * false otherwise. This property will be true if the use of assistive + * technologies, such as screen readers, has been detected. Setting this property + * to true manually enables Chrome's accessibility support, allowing developers to + * expose 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. + */ + accessibilitySupportEnabled?: boolean; + /** + * A Boolean which when true disables the overrides that Electron has in place to + * ensure renderer processes are restarted on every navigation. The current + * default value for this property is false. The intention is for these overrides + * to become disabled by default and then at some point in the future this property + * will be removed. This property impacts which native modules you can use in the + * renderer process. For more information on the direction Electron is going with + * renderer process restarts and usage of native modules in the renderer process + * please check out this Tracking Issue. + */ + allowRendererProcessReuse?: boolean; + /** + * A Menu property that return Menu if one has been set and null otherwise. Users + * can pass a Menu to set this property. + */ + applicationMenu?: Menu; commandLine: CommandLine; dock: Dock; /** @@ -1217,7 +1288,8 @@ declare namespace Electron { * media keys or browser commands, as well as the "Back" button built into some * mice on Windows. Commands are lowercased, underscores are replaced with hyphens, * and the APPCOMMAND_ prefix is stripped off. e.g. APPCOMMAND_BROWSER_BACKWARD is - * emitted as browser-backward. + * emitted as browser-backward. The following app commands are explictly supported + * on Linux: */ on(event: 'app-command', listener: (event: Event, command: string) => void): this; @@ -1551,6 +1623,10 @@ declare namespace Electron { * ready event of the app module is emitted. */ static removeExtension(name: string): void; + /** + * Replacement API for setBrowserView supporting work with multi browser views. + */ + addBrowserView(browserView: BrowserView): void; /** * Adds a window as a tab on this window, after the tab for the window instance. */ @@ -1561,11 +1637,22 @@ declare namespace Electron { blur(): void; blurWebView(): void; /** - * Same as webContents.capturePage([rect, ]callback). + * Captures a snapshot of the page within rect. Upon completion callback will be + * called with callback(image). The image is an instance of NativeImage that stores + * data of the snapshot. Omitting rect will capture the whole visible page. + * Deprecated Soon */ capturePage(callback: (image: NativeImage) => void): void; /** - * Same as webContents.capturePage([rect, ]callback). + * Captures a snapshot of the page within rect. Omitting rect will capture the + * whole visible page. + */ + capturePage(rect?: Rectangle): Promise; + /** + * Captures a snapshot of the page within rect. Upon completion callback will be + * called with callback(image). The image is an instance of NativeImage that stores + * data of the snapshot. Omitting rect will capture the whole visible page. + * Deprecated Soon */ capturePage(rect: Rectangle, callback: (image: NativeImage) => void): void; /** @@ -1598,11 +1685,13 @@ declare namespace Electron { focus(): void; focusOnWebView(): void; getBounds(): Rectangle; - /** - * Note: The BrowserView API is currently experimental and may change or be removed - * in future Electron releases. - */ getBrowserView(): (BrowserView) | (null); + /** + * Returns array of BrowserView what was an attached with addBrowserView or + * setBrowserView. Note: The BrowserView API is currently experimental and may + * change or be removed in future Electron releases. + */ + getBrowserViews(): void; getChildWindows(): BrowserWindow[]; getContentBounds(): Rectangle; getContentSize(): number[]; @@ -1626,13 +1715,10 @@ declare namespace Electron { getRepresentedFilename(): string; getSize(): number[]; /** - * Note: The title of web page can be different from the title of the native + * Note: The title of the web page can be different from the title of the native * window. */ getTitle(): string; - /** - * On Windows and Linux always returns true. - */ hasShadow(): boolean; /** * Hides the window. @@ -1684,7 +1770,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, options?: LoadFileOptions): void; + loadFile(filePath: string, options?: LoadFileOptions): Promise; /** * 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 @@ -1692,7 +1778,7 @@ declare namespace Electron { * url.format method: You can load a URL using a POST request with URL-encoded data * by doing the following: */ - loadURL(url: string, options?: LoadURLOptions): void; + loadURL(url: string, options?: LoadURLOptions): Promise; /** * Maximizes the window. This will also show (but not focus) the window if it isn't * being displayed already. @@ -1725,6 +1811,11 @@ declare namespace Electron { * Same as webContents.reload. */ reload(): void; + removeBrowserView(browserView: BrowserView): void; + /** + * Remove the window's menu bar. + */ + removeMenu(): void; /** * Restores the window from minimized state to its previous state. */ @@ -1829,7 +1920,7 @@ declare namespace Electron { */ setFullScreenable(fullscreenable: boolean): void; /** - * Sets whether the window should have a shadow. On Windows and Linux does nothing. + * Sets whether the window should have a shadow. */ setHasShadow(hasShadow: boolean): void; /** @@ -1856,8 +1947,7 @@ declare namespace Electron { */ setMaximumSize(width: number, height: number): void; /** - * Sets the menu as the window's menu bar, setting it to null will remove the menu - * bar. + * Sets the menu as the window's menu bar. */ setMenu(menu: (Menu) | (null)): void; /** @@ -2296,12 +2386,12 @@ declare namespace Electron { // Docs: http://electronjs.org/docs/api/clipboard - availableFormats(type?: string): string[]; + availableFormats(type?: 'selection' | 'clipboard'): string[]; /** * Clears the clipboard content. */ - clear(type?: string): void; - has(format: string, type?: string): boolean; + clear(type?: 'selection' | 'clipboard'): void; + has(format: string, type?: 'selection' | 'clipboard'): boolean; read(format: string): string; /** * Returns an Object containing title and url keys representing the bookmark in the @@ -2310,93 +2400,94 @@ declare namespace Electron { */ readBookmark(): ReadBookmark; readBuffer(format: string): Buffer; + /** + * This method uses synchronous IPC when called from the renderer process. The + * cached value is reread from the find pasteboard whenever the application is + * activated. + */ readFindText(): string; - readHTML(type?: string): string; - readImage(type?: string): NativeImage; - readRTF(type?: string): string; - readText(type?: string): string; + readHTML(type?: 'selection' | 'clipboard'): string; + readImage(type?: 'selection' | 'clipboard'): NativeImage; + readRTF(type?: 'selection' | 'clipboard'): string; + readText(type?: 'selection' | 'clipboard'): string; /** * Writes data to the clipboard. */ - write(data: Data, type?: string): void; + write(data: Data, type?: 'selection' | 'clipboard'): void; /** * Writes the title and url into the clipboard as a bookmark. Note: Most apps on * Windows don't support pasting bookmarks into them so you can use clipboard.write * to write both a bookmark and fallback text to the clipboard. */ - writeBookmark(title: string, url: string, type?: string): void; + writeBookmark(title: string, url: string, type?: 'selection' | 'clipboard'): void; /** * Writes the buffer into the clipboard as format. */ - writeBuffer(format: string, buffer: Buffer, type?: string): void; + writeBuffer(format: string, buffer: Buffer, type?: 'selection' | 'clipboard'): void; /** - * Writes the text into the find pasteboard as plain text. This method uses - * synchronous IPC when called from the renderer process. + * Writes the text into the find pasteboard (the pasteboard that holds information + * about the current state of the active application’s find panel) as plain text. + * This method uses synchronous IPC when called from the renderer process. */ writeFindText(text: string): void; /** * Writes markup to the clipboard. */ - writeHTML(markup: string, type?: string): void; + writeHTML(markup: string, type?: 'selection' | 'clipboard'): void; /** * Writes image to the clipboard. */ - writeImage(image: NativeImage, type?: string): void; + writeImage(image: NativeImage, type?: 'selection' | 'clipboard'): void; /** * Writes the text into the clipboard in RTF. */ - writeRTF(text: string, type?: string): void; + writeRTF(text: string, type?: 'selection' | 'clipboard'): void; /** * Writes the text into the clipboard as plain text. */ - writeText(text: string, type?: string): void; + writeText(text: string, type?: 'selection' | 'clipboard'): void; } interface ContentTracing extends EventEmitter { // Docs: http://electronjs.org/docs/api/content-tracing - /** - * Get the current monitoring traced data. Child processes typically cache trace - * data and only rarely flush and send trace data back to the main process. This is - * because it may be an expensive operation to send the trace data over IPC and we - * would like to avoid unneeded runtime overhead from tracing. So, to end tracing, - * we must asynchronously ask all child processes to flush any pending trace data. - * Once all child processes have acknowledged the captureMonitoringSnapshot request - * the callback will be called with a file that contains the traced data. - */ - captureMonitoringSnapshot(resultFilePath: string, callback: (resultFilePath: string) => void): void; /** * Get a set of category groups. The category groups can change as new code paths * are reached. Once all child processes have acknowledged the getCategories - * request the callback is invoked with an array of category groups. + * request the callback is invoked with an array of category groups. Deprecated + * Soon */ getCategories(callback: (categories: string[]) => void): void; + /** + * Get a set of category groups. The category groups can change as new code paths + * are reached. + */ + getCategories(): Promise; /** * Get the maximum usage across processes of trace buffer as a percentage of the * full state. When the TraceBufferUsage value is determined the callback is - * called. + * called. Deprecated Soon */ - getTraceBufferUsage(callback: (value: number, percentage: number) => void): void; + getTraceBufferUsage(callback: (value: number) => void): void; /** - * Start monitoring on all processes. Monitoring begins immediately locally and - * asynchronously on child processes as soon as they receive the startMonitoring - * request. Once all child processes have acknowledged the startMonitoring request - * the callback will be called. + * Get the maximum usage across processes of trace buffer as a percentage of the + * full state. */ - startMonitoring(options: StartMonitoringOptions, callback: Function): void; + getTraceBufferUsage(): Promise; /** * Start recording on all processes. Recording begins immediately locally and * asynchronously on child processes as soon as they receive the EnableRecording * request. The callback will be called once all child processes have acknowledged - * the startRecording request. + * the startRecording request. Deprecated Soon */ 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. + * Start recording on all processes. Recording begins immediately locally and + * asynchronously on child processes as soon as they receive the EnableRecording + * request. */ - stopMonitoring(callback: Function): void; + startRecording(options: (TraceCategoriesAndOptions) | (TraceConfig)): Promise; /** * Stop recording on all processes. Child processes typically cache trace data and * only rarely flush and send trace data back to the main process. This helps to @@ -2406,9 +2497,18 @@ declare namespace Electron { * acknowledged the stopRecording request, callback will be called with a file that * contains the traced data. Trace data will be written into resultFilePath if it * is not empty or into a temporary file. The actual file path will be passed to - * callback if it's not null. + * callback if it's not null. Deprecated Soon */ stopRecording(resultFilePath: string, callback: (resultFilePath: string) => void): void; + /** + * Stop recording on all processes. Child processes typically cache trace data and + * only rarely flush and send trace data back to the main process. This helps to + * minimize the runtime overhead of tracing since sending trace data over IPC can + * be an expensive operation. So, to end tracing, we must asynchronously ask all + * child processes to flush any pending trace data. Trace data will be written into + * resultFilePath if it is not empty or into a temporary file. + */ + stopRecording(resultFilePath: string): Promise; } interface Cookie { @@ -2520,20 +2620,37 @@ declare namespace Electron { /** * Writes any unwritten cookies data to disk. */ + flushStore(): Promise; + /** + * Writes any unwritten cookies data to disk. Deprecated Soon + */ flushStore(callback: Function): void; + /** + * Sends a request to get all cookies matching filter, and resolves a promise with + * the response. + */ + get(filter: Filter): Promise; /** * Sends a request to get all cookies matching filter, callback will be called with - * callback(error, cookies) on complete. + * callback(error, cookies) on complete. Deprecated Soon */ get(filter: Filter, callback: (error: Error, cookies: Cookie[]) => void): void; + /** + * Removes the cookies matching url and name + */ + remove(url: string, name: string): Promise; /** * Removes the cookies matching url and name, callback will called with callback() - * on complete. + * on complete. Deprecated Soon */ remove(url: string, name: string, callback: Function): void; + /** + * Sets a cookie with details. + */ + set(details: Details): Promise; /** * Sets a cookie with details, callback will be called with callback(error) on - * complete. + * complete. Deprecated Soon */ set(details: Details, callback: (error: Error) => void): void; } @@ -2561,15 +2678,15 @@ declare namespace Electron { id: string; } - interface CrashReporter extends EventEmitter { + interface CrashReporter { // Docs: http://electronjs.org/docs/api/crash-reporter /** * Set an extra parameter to be sent with the crash report. The values specified * here will be sent in addition to any values set via the extra option when start - * was called. This API is only available on macOS, if you need to add/update extra - * parameters on Linux and Windows after your first call to start you can call + * was called. This API is only available on macOS and windows, if you need to + * add/update extra parameters on Linux after your first call to start you can call * start again with the updated extra options. */ addExtraParameter(key: string, value: string): void; @@ -2613,13 +2730,10 @@ declare namespace Electron { * reports from them, use process.crashReporter.start instead. Pass the same * options as above along with an additional one called crashesDirectory that * should point to a directory to store the crash reports temporarily. You can test - * this out by calling process.crash() to crash the child process. Note: To collect - * crash reports from child process in Windows, you need to add this extra code as - * well. This will start the process that will monitor and send the crash reports. - * Replace submitURL, productName and crashesDirectory with appropriate values. - * Note: If you need send additional/updated extra parameters after your first call - * start you can call addExtraParameter on macOS or call start again with the - * new/updated extra parameters on Linux and Windows. Note: On macOS, Electron uses + * this out by calling process.crash() to crash the child process. Note: If you + * need send additional/updated extra parameters after your first call start you + * can call addExtraParameter on macOS or call start again with the new/updated + * extra parameters on Linux and Windows. Note: On macOS and windows, Electron uses * a new crashpad client for crash collection and reporting. If you want to enable * crash reporting, initializing crashpad from the main process using * crashReporter.start is required regardless of which process you want to collect @@ -2631,6 +2745,17 @@ declare namespace Electron { start(options: CrashReporterStartOptions): void; } + interface CustomScheme { + + // Docs: http://electronjs.org/docs/api/structures/custom-scheme + + privileges?: Privileges; + /** + * Custom schemes to be registered with options. + */ + scheme: string; + } + class Debugger extends EventEmitter { // Docs: http://electronjs.org/docs/api/debugger @@ -2712,9 +2837,13 @@ declare namespace Electron { detach(): void; isAttached(): boolean; /** - * Send given command to the debugging target. + * Send given command to the debugging target. Deprecated Soon */ sendCommand(method: string, commandParams?: any, callback?: (error: any, result: any) => void): void; + /** + * Send given command to the debugging target. + */ + sendCommand(method: string, commandParams?: any): Promise; } interface DesktopCapturer extends EventEmitter { @@ -2725,15 +2854,22 @@ declare namespace Electron { * Starts gathering information about all available desktop media sources, and * calls callback(error, sources) when finished. sources is an array of * DesktopCapturerSource objects, each DesktopCapturerSource represents a screen or - * an individual window that can be captured. + * an individual window that can be captured. Deprecated Soon */ getSources(options: SourcesOptions, callback: (error: Error, sources: DesktopCapturerSource[]) => void): void; + getSources(options: SourcesOptions): Promise; } interface DesktopCapturerSource { // Docs: http://electronjs.org/docs/api/structures/desktop-capturer-source + /** + * An icon image of the application that owns the window or null if the source has + * a type screen. The size of the icon is not known in advance and depends on what + * the the application provides. + */ + appIcon: NativeImage; /** * A unique identifier that will correspond to the id of the matching returned by * the . On some platforms, this is equivalent to the XX portion of the id field @@ -2770,7 +2906,7 @@ declare namespace Electron { * information, and gives the user the option of trusting/importing the * certificate. If you provide a browserWindow argument the dialog will be attached * to the parent window, making it modal. On Windows the options are more limited, - * due to the Win32 APIs used: + * due to the Win32 APIs used: Deprecated Soon */ showCertificateTrustDialog(browserWindow: BrowserWindow, options: CertificateTrustDialogOptions, callback: Function): void; /** @@ -2780,6 +2916,14 @@ declare namespace Electron { * to the parent window, making it modal. On Windows the options are more limited, * due to the Win32 APIs used: */ + showCertificateTrustDialog(options: CertificateTrustDialogOptions): Promise; + /** + * On macOS, this displays a modal dialog that shows a message and certificate + * information, and gives the user the option of trusting/importing the + * certificate. If you provide a browserWindow argument the dialog will be attached + * to the parent window, making it modal. On Windows the options are more limited, + * due to the Win32 APIs used: Deprecated Soon + */ showCertificateTrustDialog(options: CertificateTrustDialogOptions, callback: Function): void; /** * On macOS, this displays a modal dialog that shows a message and certificate @@ -2788,6 +2932,14 @@ declare namespace Electron { * to the parent window, making it modal. On Windows the options are more limited, * due to the Win32 APIs used: */ + showCertificateTrustDialog(browserWindow: BrowserWindow, options: CertificateTrustDialogOptions): Promise; + /** + * On macOS, this displays a modal dialog that shows a message and certificate + * information, and gives the user the option of trusting/importing the + * certificate. If you provide a browserWindow argument the dialog will be attached + * to the parent window, making it modal. On Windows the options are more limited, + * due to the Win32 APIs used: Deprecated Soon + */ showCertificateTrustDialog(browserWindow: BrowserWindow, options: CertificateTrustDialogOptions, callback: Function): void; /** * Displays a modal dialog that shows an error message. This API can be called @@ -2798,73 +2950,140 @@ declare namespace Electron { showErrorBox(title: string, content: string): void; /** * Shows a message box, it will block the process until the message box is closed. - * It returns the index of the clicked button. The browserWindow argument allows - * the dialog to attach itself to a parent window, making it modal. If a callback - * is passed, the dialog will not block the process. The API call will be - * asynchronous and the result will be passed via callback(response). + * The browserWindow argument allows the dialog to attach itself to a parent + * window, making it modal. */ - showMessageBox(browserWindow: BrowserWindow, options: MessageBoxOptions, callback?: (response: number, checkboxChecked: boolean) => void): number; + showMessageBox(browserWindow: BrowserWindow, options: MessageBoxOptions): Promise; + /** + * Shows a message box, it will block the process until the message box is closed. + * The browserWindow argument allows the dialog to attach itself to a parent + * window, making it modal. + */ + showMessageBox(options: MessageBoxOptions): Promise; /** * Shows a message box, it will block the process until the message box is closed. * It returns the index of the clicked button. The browserWindow argument allows - * the dialog to attach itself to a parent window, making it modal. If a callback - * is passed, the dialog will not block the process. The API call will be - * asynchronous and the result will be passed via callback(response). + * the dialog to attach itself to a parent window, making it modal. */ - showMessageBox(options: MessageBoxOptions, callback?: (response: number, checkboxChecked: boolean) => void): number; + showMessageBoxSync(browserWindow: BrowserWindow, options: MessageBoxSyncOptions): number; + /** + * Shows a message box, it will block the process until the message box is closed. + * It returns the index of the clicked button. The browserWindow argument allows + * the dialog to attach itself to a parent window, making it modal. + */ + showMessageBoxSync(options: MessageBoxSyncOptions): number; /** * 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 * be displayed or selected when you want to limit the user to a specific type. For * example: The extensions array should contain extensions without wildcards or * dots (e.g. 'png' is good but '.png' and '*.png' are bad). To show all files, use - * the '*' wildcard (no other wildcard is supported). If a callback is passed, the - * API call will be asynchronous and the result will be passed via - * callback(filenames). Note: On Windows and Linux an open dialog can not be both a - * file selector and a directory selector, so if you set properties to ['openFile', - * 'openDirectory'] on these platforms, a directory selector will be shown. + * the '*' wildcard (no other wildcard is supported). Note: On Windows and Linux an + * open dialog can not be both a 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[]) | (undefined); + showOpenDialog(browserWindow: BrowserWindow, options: OpenDialogOptions, callback?: Function): Promise; /** * 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 * be displayed or selected when you want to limit the user to a specific type. For * example: The extensions array should contain extensions without wildcards or * dots (e.g. 'png' is good but '.png' and '*.png' are bad). To show all files, use - * the '*' wildcard (no other wildcard is supported). If a callback is passed, the - * API call will be asynchronous and the result will be passed via - * callback(filenames). Note: On Windows and Linux an open dialog can not be both a - * file selector and a directory selector, so if you set properties to ['openFile', - * 'openDirectory'] on these platforms, a directory selector will be shown. + * the '*' wildcard (no other wildcard is supported). Note: On Windows and Linux an + * open dialog can not be both a 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[]) | (undefined); + showOpenDialog(options: OpenDialogOptions, callback?: Function): Promise; /** * 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 - * be displayed, see dialog.showOpenDialog for an example. If a callback is passed, - * the API call will be asynchronous and the result will be passed via - * callback(filename). + * be displayed or selected when you want to limit the user to a specific type. For + * example: The extensions array should contain extensions without wildcards or + * dots (e.g. 'png' is good but '.png' and '*.png' are bad). To show all files, use + * the '*' wildcard (no other wildcard is supported). Note: On Windows and Linux an + * open dialog can not be both a file selector and a directory selector, so if you + * set properties to ['openFile', 'openDirectory'] on these platforms, a directory + * selector will be shown. */ - showSaveDialog(browserWindow: BrowserWindow, options: SaveDialogOptions, callback?: (filename: string, bookmark: string) => void): (string) | (undefined); + showOpenDialogSync(browserWindow: BrowserWindow, options: OpenDialogSyncOptions): (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 - * be displayed, see dialog.showOpenDialog for an example. If a callback is passed, - * the API call will be asynchronous and the result will be passed via - * callback(filename). + * be displayed or selected when you want to limit the user to a specific type. For + * example: The extensions array should contain extensions without wildcards or + * dots (e.g. 'png' is good but '.png' and '*.png' are bad). To show all files, use + * the '*' wildcard (no other wildcard is supported). Note: On Windows and Linux an + * open dialog can not be both a file selector and a directory selector, so if you + * set properties to ['openFile', 'openDirectory'] on these platforms, a directory + * selector will be shown. */ - showSaveDialog(options: SaveDialogOptions, callback?: (filename: string, bookmark: string) => void): (string) | (undefined); + showOpenDialogSync(options: OpenDialogSyncOptions): (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 + * be displayed, see dialog.showOpenDialog for an example. Note: On macOS, using + * the asynchronous version is recommended to avoid issues when expanding and + * collapsing the dialog. + */ + showSaveDialog(options: SaveDialogOptions): Promise; + /** + * 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 + * be displayed, see dialog.showOpenDialog for an example. Note: On macOS, using + * the asynchronous version is recommended to avoid issues when expanding and + * collapsing the dialog. + */ + showSaveDialog(browserWindow: BrowserWindow, options: SaveDialogOptions): Promise; + /** + * 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 + * be displayed, see dialog.showOpenDialog for an example. + */ + showSaveDialogSync(options: SaveDialogSyncOptions): (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 + * be displayed, see dialog.showOpenDialog for an example. + */ + showSaveDialogSync(browserWindow: BrowserWindow, options: SaveDialogSyncOptions): (string) | (undefined); } interface Display { // Docs: http://electronjs.org/docs/api/structures/display + /** + * Can be available, unavailable, unknown. + */ + accelerometerSupport: ('available' | 'unavailable' | 'unknown'); bounds: Rectangle; + /** + * The number of bits per pixel. + */ + colorDepth: number; + /** + * represent a color space (three-dimensional object which contains all realizable + * color combinations) for the purpose of color conversions + */ + colorSpace: string; + /** + * The number of bits per color component. + */ + depthPerComponent: number; /** * Unique identifier associated with the display. */ id: number; + /** + * true for an internal display and false for an external display + */ + internal: boolean; + /** + * Whether or not the display is a monochrome display. + */ + monochrome: boolean; /** * Can be 0, 90, 180, 270, represents screen rotation in clock-wise degrees. */ @@ -2951,6 +3170,7 @@ declare namespace Electron { getLastModifiedTime(): string; getMimeType(): string; getReceivedBytes(): number; + getSaveDialogOptions(): SaveDialogOptions; getSavePath(): string; getStartTime(): number; /** @@ -2977,6 +3197,12 @@ declare namespace Electron { * received bytes and restart the download from the beginning. */ resume(): void; + /** + * This API allows the user to set custom options for the save dialog that opens + * for the download item by default. The API is only available in session's + * will-download callback function. + */ + setSaveDialogOptions(options: SaveDialogOptions): void; /** * The API is only available in session's will-download callback function. If user * doesn't set the save path via the API, Electron will use the original routine to @@ -2985,6 +3211,13 @@ declare namespace Electron { setSavePath(path: string): void; } + interface Event extends GlobalEvent { + + // Docs: http://electronjs.org/docs/api/structures/event + + preventDefault: (() => void); + } + interface FileFilter { // Docs: http://electronjs.org/docs/api/structures/file-filter @@ -3012,7 +3245,17 @@ declare namespace Electron { * on macOS 10.14 Mojave unless the app has been authorized as a trusted * accessibility client: */ - register(accelerator: Accelerator, callback: Function): void; + register(accelerator: Accelerator, callback: Function): boolean; + /** + * Registers a global shortcut of all accelerator items in accelerators. The + * callback is called when any of the registered shortcuts are pressed by the user. + * When a given accelerator is already taken by other applications, this call will + * silently fail. This behavior is intended by operating systems, since they don't + * want applications to fight for global shortcuts. The following accelerators will + * not be registered successfully on macOS 10.14 Mojave unless the app has been + * authorized as a trusted accessibility client: + */ + registerAll(accelerators: string[], callback: Function): void; /** * Unregisters the global shortcut of accelerator. */ @@ -3118,15 +3361,24 @@ declare namespace Electron { */ finishTransactionByDate(date: string): void; /** - * Retrieves the product descriptions. + * Retrieves the product descriptions. Deprecated Soon */ getProducts(productIDs: string[], callback: (products: Product[]) => void): void; + /** + * Retrieves the product descriptions. + */ + getProducts(productIDs: string[]): Promise; getReceiptURL(): string; + /** + * You should listen for the transactions-updated event as soon as possible and + * certainly before you call purchaseProduct. Deprecated Soon + */ + purchaseProduct(productID: string, quantity?: number, callback?: (isProductValid: boolean) => void): void; /** * You should listen for the transactions-updated event as soon as possible and * certainly before you call purchaseProduct. */ - purchaseProduct(productID: string, quantity?: number, callback?: (isProductValid: boolean) => void): void; + purchaseProduct(productID: string, quantity?: number): Promise; } class IncomingMessage extends EventEmitter { @@ -3228,12 +3480,12 @@ declare namespace Electron { * Listens to channel, when a new message arrives listener would be called with * listener(event, args...). */ - on(channel: string, listener: Function): this; + on(channel: string, listener: (event: IpcMainEvent, ...args: any[]) => void): this; /** * Adds a one time listener function for the event. This listener is invoked only * the next time a message is sent to channel, after which it is removed. */ - once(channel: string, listener: Function): this; + once(channel: string, listener: (event: IpcMainEvent, ...args: any[]) => void): this; /** * Removes listeners of the specified channel. */ @@ -3245,6 +3497,31 @@ declare namespace Electron { removeListener(channel: string, listener: Function): this; } + interface IpcMainEvent extends Event { + + // Docs: http://electronjs.org/docs/api/structures/ipc-main-event + + /** + * The ID of the renderer frame that sent this message + */ + frameId: number; + /** + * A function that will send an IPC message to the renderer frame that sent the + * original message that you are currently handling. You should use this method to + * "reply" to the sent message in order to guaruntee the reply will go to the + * correct process and frame. + */ + reply: Function; + /** + * Set this to the value to be returned in a syncronous message + */ + returnValue: any; + /** + * Returns the webContents that sent the message + */ + sender: WebContents; + } + interface IpcRenderer extends EventEmitter { // Docs: http://electronjs.org/docs/api/ipc-renderer @@ -3253,12 +3530,12 @@ declare namespace Electron { * Listens to channel, when a new message arrives listener would be called with * listener(event, args...). */ - on(channel: string, listener: Function): this; + on(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): this; /** * Adds a one time listener function for the event. This listener is invoked only * the next time a message is sent to channel, after which it is removed. */ - once(channel: string, listener: Function): this; + once(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): this; /** * Removes all listeners, or those of the specified channel. */ @@ -3295,6 +3572,23 @@ declare namespace Electron { sendToHost(channel: string, ...args: any[]): void; } + interface IpcRendererEvent extends Event { + + // Docs: http://electronjs.org/docs/api/structures/ipc-renderer-event + + /** + * The IpcRenderer instance that emitted the event originally + */ + sender: IpcRenderer; + /** + * The webContents.id that sent the message, you can call + * event.sender.sendTo(event.senderId, ...) to reply to the message, see for more + * information. This only applies to messages sent from a different renderer. + * Messages sent directly from the main process set event.senderId to 0. + */ + senderId: number; + } + interface JumpListCategory { // Docs: http://electronjs.org/docs/api/structures/jump-list-category @@ -3358,6 +3652,37 @@ declare namespace Electron { * One of the following: */ type?: ('task' | 'separator' | 'file'); + /** + * The working directory. Default is empty. + */ + workingDirectory?: string; + } + + interface KeyboardEvent extends Event { + + // Docs: http://electronjs.org/docs/api/structures/keyboard-event + + /** + * whether an Alt key was used in an accelerator to trigger the Event + */ + altKey?: boolean; + /** + * whether the Control key was used in an accelerator to trigger the Event + */ + ctrlKey?: boolean; + /** + * whether a meta key was used in an accelerator to trigger the Event + */ + metaKey?: boolean; + /** + * whether a Shift key was used in an accelerator to trigger the Event + */ + shiftKey?: boolean; + /** + * whether an accelerator was used to trigger the event as opposed to another user + * gesture like mouse click + */ + triggeredByAccelerator?: boolean; } interface MemoryUsageDetails { @@ -3393,7 +3718,7 @@ declare namespace Electron { * usage can be referenced above. You can also attach other fields to the element * of the template and they will become properties of the constructed menu items. */ - static buildFromTemplate(template: MenuItemConstructorOptions[]): Menu; + static buildFromTemplate(template: Array<(MenuItemConstructorOptions) | (MenuItem)>): Menu; /** * Note: The returned Menu instance doesn't support dynamic addition or removal of * menu items. Instance properties can still be dynamically modified. @@ -3408,9 +3733,16 @@ declare namespace Electron { static sendActionToFirstResponder(action: string): void; /** * Sets menu as the application menu on macOS. On Windows and Linux, the menu will - * be set as each window's top menu. Passing null will remove the menu bar on - * Windows and Linux but has no effect on macOS. Note: This API has to be called - * after the ready event of app module. + * be set as each window's top menu. Also on Windows and Linux, you can use a & in + * the top-level item name to indicate which letter should get a generated + * accelerator. For example, using &File for the file menu would result in a + * generated Alt-F accelerator that opens the associated menu. The indicated + * character in the button label gets an underline. The & character is not + * displayed on the button label. Passing null will suppress the default menu. On + * Windows and Linux, this has the additional effect of removing the menu bar from + * the window. Note: The default menu will be created automatically if the app does + * not set one. It contains standard items such as File, Edit, View, Window and + * Help. */ static setApplicationMenu(menu: (Menu) | (null)): void; /** @@ -3438,10 +3770,20 @@ declare namespace Electron { // Docs: http://electronjs.org/docs/api/menu-item constructor(options: MenuItemConstructorOptions); + accelerator: string; checked: boolean; click: Function; + commandId: number; enabled: boolean; + icon: NativeImage; + id: string; label: string; + menu: Menu; + registerAccelerator: boolean; + role: string; + sublabel: string; + submenu: Menu; + type: string; visible: boolean; } @@ -3468,7 +3810,13 @@ declare namespace Electron { */ static createEmpty(): NativeImage; /** - * Creates a new NativeImage instance from buffer. + * Creates a new NativeImage instance from buffer that contains the raw bitmap + * pixel data returned by toBitmap(). The specific format is platform-dependent. + */ + static createFromBitmap(buffer: Buffer, options: CreateFromBitmapOptions): NativeImage; + /** + * Creates a new NativeImage instance from buffer. Tries to decode as PNG or JPEG + * first. */ static createFromBuffer(buffer: Buffer, options?: CreateFromBufferOptions): NativeImage; /** @@ -3556,9 +3904,14 @@ declare namespace Electron { startLogging(path: string): void; /** * Stops recording network events. If not called, net logging will automatically - * end when app quits. + * end when app quits. Deprecated Soon */ stopLogging(callback?: (path: string) => void): void; + /** + * Stops recording network events. If not called, net logging will automatically + * end when app quits. + */ + stopLogging(): Promise; /** * A Boolean property that indicates whether network logs are recorded. */ @@ -3737,6 +4090,15 @@ declare namespace Electron { once(event: 'unlock-screen', listener: Function): this; addListener(event: 'unlock-screen', listener: Function): this; removeListener(event: 'unlock-screen', listener: Function): this; + /** + * Calculate the system idle state. idleThreshold is the amount of time (in + * seconds) before considered idle. locked is available on supported systems only. + */ + getSystemIdleState(idleThreshold: number): ('active' | 'idle' | 'locked' | 'unknown'); + /** + * Calculate system idle time in seconds. + */ + getSystemIdleTime(): number; /** * Calculate the system idle state. idleThreshold is the amount of time (in * seconds) before considered idle. callback will be called synchronously on some @@ -3782,6 +4144,26 @@ declare namespace Electron { status: number; } + interface ProcessMemoryInfo { + + // Docs: http://electronjs.org/docs/api/structures/process-memory-info + + /** + * The amount of memory not shared by other processes, such as JS heap or HTML + * content in Kilobytes. + */ + private: number; + /** + * and The amount of memory currently pinned to actual physical RAM in Kilobytes. + */ + residentSet: number; + /** + * The amount of memory shared between processes, typically memory consumed by the + * Electron code itself in Kilobytes. + */ + shared: number; + } + interface ProcessMetric { // Docs: http://electronjs.org/docs/api/structures/process-metric @@ -3795,9 +4177,9 @@ declare namespace Electron { */ pid: number; /** - * Process type (Browser or Tab or GPU etc). + * Process type. One of the following values: */ - type: string; + type: ('Browser' | 'Tab' | 'Utility' | 'Zygote' | 'GPU' | 'Unknown'); } interface Product { @@ -3812,15 +4194,16 @@ declare namespace Electron { * A string that identifies the version of the content. */ contentVersion: string; - /** - * A Boolean value that indicates whether the App Store has downloadable content - * for this product. - */ - downloadable: boolean; /** * The locale formatted price of the product. */ formattedPrice: string; + /** + * A Boolean value that indicates whether the App Store has downloadable content + * for this product. true if at least one file has been associated with the + * product. + */ + isDownloadable: boolean; /** * A description of the product. */ @@ -3870,9 +4253,10 @@ declare namespace Electron { interceptStringProtocol(scheme: string, handler: (request: InterceptStringProtocolRequest, callback: (data?: string) => void) => void, completion?: (error: Error) => void): void; /** * The callback will be called with a boolean that indicates whether there is - * already a handler for scheme. + * already a handler for scheme. Deprecated Soon */ - isProtocolHandled(scheme: string, callback: (error: Error) => void): void; + isProtocolHandled(scheme: string, callback: (handled: boolean) => void): void; + isProtocolHandled(scheme: string): Promise; /** * Registers a protocol of scheme that will send a Buffer as a response. The usage * is the same with registerFileProtocol, except that the callback should be called @@ -3887,13 +4271,14 @@ declare namespace Electron { * 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 - * 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 - * than protocols that follow the "generic URI syntax" like file:, so you probably - * want to call protocol.registerStandardSchemes to have your scheme treated as a - * standard scheme. + * filePath }). The object may also have a headers property which gives a map of + * headers to values for the response headers, e.g. callback({ path: filePath, + * headers: {"Content-Security-Policy": "default-src 'none'"]}). 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 than protocols that follow the + * "generic URI syntax" like file:. */ registerFileProtocol(scheme: string, handler: (request: RegisterFileProtocolRequest, callback: (filePath?: string) => void) => void, completion?: (error: Error) => void): void; /** @@ -3905,24 +4290,31 @@ declare namespace Electron { * set session to null. For POST requests the uploadData object must be provided. */ registerHttpProtocol(scheme: string, handler: (request: RegisterHttpProtocolRequest, callback: (redirectRequest: RedirectRequest) => void) => void, completion?: (error: Error) => void): void; - registerServiceWorkerSchemes(schemes: string[]): void; /** - * A standard scheme adheres to what RFC 3986 calls generic URI syntax. For example - * http and https are standard schemes, while file is not. Registering a scheme as - * standard, will allow relative and absolute resources to be resolved correctly - * when served. Otherwise the scheme will behave like the file protocol, but - * without the ability to resolve relative URLs. For example when you load - * following page with custom protocol without registering it as standard scheme, - * the image will not be loaded because non-standard schemes can not recognize - * relative URLs: Registering a scheme as standard will allow access to files - * through the FileSystem API. Otherwise the renderer will throw a security error - * for the scheme. By default web storage apis (localStorage, sessionStorage, - * webSQL, indexedDB, cookies) are disabled for non standard schemes. So in general - * if you want to register a custom protocol to replace the http protocol, you have - * to register it as a standard scheme: Note: This method can only be used before - * the ready event of the app module gets emitted. + * Note: This method can only be used before the ready event of the app module gets + * emitted and can be called only once. Registers the scheme as standard, secure, + * bypasses content security policy for resources, allows registering ServiceWorker + * and supports fetch API. Specify a privilege with the value of true to enable the + * capability. An example of registering a privileged scheme, with bypassing + * Content Security Policy: A standard scheme adheres to what RFC 3986 calls + * generic URI syntax. For example http and https are standard schemes, while file + * is not. Registering a scheme as standard, will allow relative and absolute + * resources to be resolved correctly when served. Otherwise the scheme will behave + * like the file protocol, but without the ability to resolve relative URLs. For + * example when you load following page with custom protocol without registering it + * as standard scheme, the image will not be loaded because non-standard schemes + * can not recognize relative URLs: Registering a scheme as standard will allow + * access to files through the FileSystem API. Otherwise the renderer will throw a + * security error for the scheme. By default web storage apis (localStorage, + * sessionStorage, webSQL, indexedDB, cookies) are disabled for non standard + * schemes. So in general if you want to register a custom protocol to replace the + * http protocol, you have to register it as a standard scheme. + * protocol.registerSchemesAsPrivileged can be used to replicate the functionality + * of the previous protocol.registerStandardSchemes, webFrame.registerURLSchemeAs* + * and protocol.registerServiceWorkerSchemes functions that existed prior to + * Electron 5.0.0, for example: before (<= v4.x) after (>= v5.x) */ - registerStandardSchemes(schemes: string[], options?: RegisterStandardSchemesOptions): void; + registerSchemesAsPrivileged(customSchemes: CustomScheme[]): void; /** * Registers a protocol of scheme that will send a Readable as a response. The * usage is similar to the other register{Any}Protocol, except that the callback @@ -4203,22 +4595,33 @@ declare namespace Electron { * authentication. */ allowNTLMCredentialsForDomains(domains: string): void; + clearAuthCache(): Promise; + clearAuthCache(options: (RemovePassword) | (RemoveClientCertificate)): Promise; /** - * Clears the session’s HTTP authentication cache. + * Clears the session’s HTTP authentication cache. Deprecated Soon */ - clearAuthCache(options: (RemovePassword) | (RemoveClientCertificate), callback?: Function): void; + clearAuthCache(options: (RemovePassword) | (RemoveClientCertificate), callback: Function): void; /** * Clears the session’s HTTP cache. */ - clearCache(callback: Function): void; + clearCache(): Promise; + /** + * Clears the session’s HTTP cache. Deprecated Soon + */ + clearCache(callback: (error: number) => void): void; /** * Clears the host resolver cache. */ + clearHostResolverCache(): Promise; + /** + * Clears the host resolver cache. Deprecated Soon + */ clearHostResolverCache(callback?: Function): void; /** - * Clears the data of web storages. + * Clears the storage data for the current session. Deprecated Soon */ clearStorageData(options?: ClearStorageDataOptions, callback?: Function): void; + clearStorageData(options?: ClearStorageDataOptions): Promise; /** * Allows resuming cancelled or interrupted downloads from previous Session. The * API will generate a DownloadItem that can be accessed with the will-download @@ -4240,16 +4643,22 @@ declare namespace Electron { * Writes any unwritten DOMStorage data to disk. */ flushStorageData(): void; - getBlobData(identifier: string, callback: (result: Buffer) => void): void; /** - * Callback is invoked with the session's current cache size. + * Deprecated Soon */ - getCacheSize(callback: (size: number) => void): void; + getBlobData(identifier: string, callback: (result: Buffer) => void): void; + getBlobData(identifier: string): Promise; + getCacheSize(): Promise; + /** + * Callback is invoked with the session's current cache size. Deprecated Soon + */ + getCacheSize(callback: (size: number, error: number) => void): void; getPreloads(): string[]; getUserAgent(): string; + resolveProxy(url: string): Promise; /** * Resolves the proxy information for url. The callback will be called with - * callback(proxy) when the request is performed. + * callback(proxy) when the request is performed. Deprecated Soon */ resolveProxy(url: string, callback: (proxy: string) => void): void; /** @@ -4288,6 +4697,13 @@ declare namespace Electron { * proxyRules has to follow the rules below: For example: The proxyBypassRules is a * comma separated list of rules described below: */ + setProxy(config: Config): Promise; + /** + * Sets the proxy settings. When pacScript and proxyRules are provided together, + * the proxyRules option is ignored and pacScript configuration is applied. The + * proxyRules has to follow the rules below: For example: The proxyBypassRules is a + * comma separated list of rules described below: Deprecated Soon + */ setProxy(config: Config, callback: Function): void; /** * Overrides the userAgent and acceptLanguages for this session. The @@ -4319,7 +4735,12 @@ declare namespace Electron { * Open the given external protocol URL in the desktop's default manner. (For * example, mailto: URLs in the user's default mail agent). */ - openExternal(url: string, options?: OpenExternalOptions, callback?: (error: Error) => void): boolean; + openExternal(url: string, options?: OpenExternalOptions): Promise; + /** + * Open the given external protocol URL in the desktop's default manner. (For + * example, mailto: URLs in the user's default mail agent). Deprecated + */ + openExternalSync(url: string, options?: OpenExternalSyncOptions): boolean; /** * Open the given file in the desktop's default manner. */ @@ -4332,7 +4753,7 @@ declare namespace Electron { /** * Show the given file in a file manager. If possible, select the file. */ - showItemInFolder(fullPath: string): boolean; + showItemInFolder(fullPath: string): void; /** * Creates or updates a shortcut link at shortcutPath. */ @@ -4433,28 +4854,48 @@ declare namespace Electron { once(event: 'color-changed', listener: (event: Event) => void): this; addListener(event: 'color-changed', listener: (event: Event) => void): this; removeListener(event: 'color-changed', listener: (event: Event) => void): this; + on(event: 'high-contrast-color-scheme-changed', listener: (event: Event, + /** + * `true` if a high contrast theme is being used, `false` otherwise. + */ + highContrastColorScheme: boolean) => void): this; + once(event: 'high-contrast-color-scheme-changed', listener: (event: Event, + /** + * `true` if a high contrast theme is being used, `false` otherwise. + */ + highContrastColorScheme: boolean) => void): this; + addListener(event: 'high-contrast-color-scheme-changed', listener: (event: Event, + /** + * `true` if a high contrast theme is being used, `false` otherwise. + */ + highContrastColorScheme: boolean) => void): this; + removeListener(event: 'high-contrast-color-scheme-changed', listener: (event: Event, + /** + * `true` if a high contrast theme is being used, `false` otherwise. + */ + highContrastColorScheme: boolean) => void): this; on(event: 'inverted-color-scheme-changed', listener: (event: Event, /** - * `true` if an inverted color scheme, such as a high contrast theme, is being - * used, `false` otherwise. + * `true` if an inverted color scheme (a high contrast color scheme with light text + * and dark backgrounds) is being used, `false` otherwise. */ invertedColorScheme: boolean) => void): this; once(event: 'inverted-color-scheme-changed', listener: (event: Event, /** - * `true` if an inverted color scheme, such as a high contrast theme, is being - * used, `false` otherwise. + * `true` if an inverted color scheme (a high contrast color scheme with light text + * and dark backgrounds) is being used, `false` otherwise. */ invertedColorScheme: boolean) => void): this; addListener(event: 'inverted-color-scheme-changed', listener: (event: Event, /** - * `true` if an inverted color scheme, such as a high contrast theme, is being - * used, `false` otherwise. + * `true` if an inverted color scheme (a high contrast color scheme with light text + * and dark backgrounds) is being used, `false` otherwise. */ invertedColorScheme: boolean) => void): this; removeListener(event: 'inverted-color-scheme-changed', listener: (event: Event, /** - * `true` if an inverted color scheme, such as a high contrast theme, is being - * used, `false` otherwise. + * `true` if an inverted color scheme (a high contrast color scheme with light text + * and dark backgrounds) is being used, `false` otherwise. */ invertedColorScheme: boolean) => void): this; /** @@ -4467,15 +4908,26 @@ declare namespace Electron { * 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; + askForMediaAccess(mediaType: 'microphone' | 'camera'): Promise; + /** + * NOTE: This API will return false on macOS systems older than Sierra 10.12.2. + */ + canPromptTouchID(): boolean; + /** + * This API is only available on macOS 10.14 Mojave or newer. + */ getAccentColor(): string; + /** + * Returns an object with system animation settings. + */ + getAnimationSettings(): AnimationSettings; /** * 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; + getColor(color: '3d-dark-shadow' | '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' | 'alternate-selected-control-text' | 'alternate-selected-control-text' | 'control-background' | 'control' | 'control-text' | 'disabled-control-text' | 'find-highlight' | 'grid' | 'header-text' | 'highlight' | 'keyboard-focus-indicator' | 'label' | 'link' | 'placeholder-text' | 'quaternary-label' | 'scrubber-textured-background' | 'secondary-label' | 'selected-content-background' | 'selected-control' | 'selected-control-text' | 'selected-menu-item' | 'selected-text-background' | 'selected-text' | 'separator' | 'shadow' | 'tertiary-label' | 'text-background' | 'text' | 'under-page-background' | 'unemphasized-selected-content-background' | 'unemphasized-selected-text-background' | 'unemphasized-selected-text' | 'window-background' | 'window-frame-text'): string; /** * Gets the macOS appearance setting that is currently applied to your application, * maps to NSApplication.effectiveAppearance Please note that until Electron is @@ -4492,6 +4944,12 @@ declare namespace Electron { * always return granted if your system is running 10.13 High Sierra or lower. */ getMediaAccessStatus(mediaType: string): ('not-determined' | 'granted' | 'denied' | 'restricted' | 'unknown'); + /** + * Returns one of several standard system colors that automatically adapt to + * vibrancy and changes in accessibility settings like 'Increase contrast' and + * 'Reduce transparency'. See Apple Documentation for more details. + */ + getSystemColor(color: 'blue' | 'brown' | 'gray' | 'green' | 'orange' | 'pink' | 'purple' | 'red' | 'yellow'): void; /** * Some popular key and types are: */ @@ -4502,6 +4960,7 @@ declare namespace Electron { */ isAeroGlassEnabled(): boolean; isDarkMode(): boolean; + isHighContrastColorScheme(): boolean; isInvertedColorScheme(): boolean; isSwipeTrackingFromScrollEventsEnabled(): boolean; isTrustedAccessibilityClient(prompt: boolean): boolean; @@ -4514,12 +4973,22 @@ declare namespace Electron { * Posts event as native notifications of macOS. The userInfo is an Object that * contains the user information dictionary sent along with the notification. */ - postNotification(event: string, userInfo: any): void; + postNotification(event: string, userInfo: any, deliverImmediately?: boolean): void; /** * Posts event as native notifications of macOS. The userInfo is an Object that * contains the user information dictionary sent along with the notification. */ postWorkspaceNotification(event: string, userInfo: any): void; + /** + * This API itself will not protect your user data; rather, it is a mechanism to + * allow you to do so. Native apps will need to set Access Control Constants like + * kSecAccessControlUserPresence on the their keychain entry so that reading it + * would auto-prompt for Touch ID biometric consent. This could be done with + * node-keytar, such that one would store an encryption key with node-keytar and + * only fetch it if promptTouchID() resolves. NOTE: This API will return a rejected + * Promise on macOS systems older than Sierra 10.12.2. + */ + promptTouchID(reason: string): Promise; /** * Add the specified defaults to your application's NSUserDefaults. */ @@ -4608,6 +5077,10 @@ declare namespace Electron { * The string to be displayed in a JumpList. */ title: string; + /** + * The working directory. Default is empty. + */ + workingDirectory?: string; } interface ThumbarButton { @@ -4829,7 +5302,7 @@ declare namespace Electron { /** * Emitted when the tray icon is clicked. */ - on(event: 'click', listener: (event: Event, + on(event: 'click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ @@ -4838,7 +5311,7 @@ declare namespace Electron { * The position of the event. */ position: Point) => void): this; - once(event: 'click', listener: (event: Event, + once(event: 'click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ @@ -4847,7 +5320,7 @@ declare namespace Electron { * The position of the event. */ position: Point) => void): this; - addListener(event: 'click', listener: (event: Event, + addListener(event: 'click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ @@ -4856,7 +5329,7 @@ declare namespace Electron { * The position of the event. */ position: Point) => void): this; - removeListener(event: 'click', listener: (event: Event, + removeListener(event: 'click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ @@ -4868,22 +5341,22 @@ declare namespace Electron { /** * Emitted when the tray icon is double clicked. */ - on(event: 'double-click', listener: (event: Event, + on(event: 'double-click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ bounds: Rectangle) => void): this; - once(event: 'double-click', listener: (event: Event, + once(event: 'double-click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ bounds: Rectangle) => void): this; - addListener(event: 'double-click', listener: (event: Event, + addListener(event: 'double-click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ bounds: Rectangle) => void): this; - removeListener(event: 'double-click', listener: (event: Event, + removeListener(event: 'double-click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ @@ -4965,22 +5438,22 @@ declare namespace Electron { /** * Emitted when the mouse enters the tray icon. */ - on(event: 'mouse-enter', listener: (event: Event, + on(event: 'mouse-enter', listener: (event: KeyboardEvent, /** * The position of the event. */ position: Point) => void): this; - once(event: 'mouse-enter', listener: (event: Event, + once(event: 'mouse-enter', listener: (event: KeyboardEvent, /** * The position of the event. */ position: Point) => void): this; - addListener(event: 'mouse-enter', listener: (event: Event, + addListener(event: 'mouse-enter', listener: (event: KeyboardEvent, /** * The position of the event. */ position: Point) => void): this; - removeListener(event: 'mouse-enter', listener: (event: Event, + removeListener(event: 'mouse-enter', listener: (event: KeyboardEvent, /** * The position of the event. */ @@ -4988,22 +5461,22 @@ declare namespace Electron { /** * Emitted when the mouse exits the tray icon. */ - on(event: 'mouse-leave', listener: (event: Event, + on(event: 'mouse-leave', listener: (event: KeyboardEvent, /** * The position of the event. */ position: Point) => void): this; - once(event: 'mouse-leave', listener: (event: Event, + once(event: 'mouse-leave', listener: (event: KeyboardEvent, /** * The position of the event. */ position: Point) => void): this; - addListener(event: 'mouse-leave', listener: (event: Event, + addListener(event: 'mouse-leave', listener: (event: KeyboardEvent, /** * The position of the event. */ position: Point) => void): this; - removeListener(event: 'mouse-leave', listener: (event: Event, + removeListener(event: 'mouse-leave', listener: (event: KeyboardEvent, /** * The position of the event. */ @@ -5011,22 +5484,22 @@ declare namespace Electron { /** * Emitted when the mouse moves in the tray icon. */ - on(event: 'mouse-move', listener: (event: Event, + on(event: 'mouse-move', listener: (event: KeyboardEvent, /** * The position of the event. */ position: Point) => void): this; - once(event: 'mouse-move', listener: (event: Event, + once(event: 'mouse-move', listener: (event: KeyboardEvent, /** * The position of the event. */ position: Point) => void): this; - addListener(event: 'mouse-move', listener: (event: Event, + addListener(event: 'mouse-move', listener: (event: KeyboardEvent, /** * The position of the event. */ position: Point) => void): this; - removeListener(event: 'mouse-move', listener: (event: Event, + removeListener(event: 'mouse-move', listener: (event: KeyboardEvent, /** * The position of the event. */ @@ -5034,22 +5507,22 @@ declare namespace Electron { /** * Emitted when the tray icon is right clicked. */ - on(event: 'right-click', listener: (event: Event, + on(event: 'right-click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ bounds: Rectangle) => void): this; - once(event: 'right-click', listener: (event: Event, + once(event: 'right-click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ bounds: Rectangle) => void): this; - addListener(event: 'right-click', listener: (event: Event, + addListener(event: 'right-click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ bounds: Rectangle) => void): this; - removeListener(event: 'right-click', listener: (event: Event, + removeListener(event: 'right-click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ @@ -5068,6 +5541,7 @@ declare namespace Electron { */ getBounds(): Rectangle; getIgnoreDoubleClickEvents(): boolean; + getTitle(title: string): string; isDestroyed(): boolean; /** * Pops up the context menu of the tray icon. When menu is passed, the menu will be @@ -5080,9 +5554,9 @@ declare namespace Electron { */ 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 - * 'always' modes when the window visibility changes. + * Sets when the tray's icon background becomes highlighted (in blue). Deprecated + * Note: You can use highlightMode with a BrowserWindow by toggling between 'never' + * and 'always' modes when the window visibility changes. */ setHighlightMode(mode: 'selection' | 'always' | 'never'): void; /** @@ -5100,7 +5574,7 @@ declare namespace Electron { */ setPressedImage(image: (NativeImage) | (string)): void; /** - * Sets the title displayed aside of the tray icon in the status bar (Support ANSI + * Sets the title displayed next to the tray icon in the status bar (Support ANSI * colors). */ setTitle(title: string): void; @@ -5368,6 +5842,14 @@ declare namespace Electron { * coordinates of the custom cursor's hotspot. */ hotspot?: Point) => void): this; + /** + * Emitted when desktopCapturer.getSources() is called in the renderer process. + * Calling event.preventDefault() will make it return empty sources. + */ + on(event: 'desktop-capturer-get-sources', listener: (event: Event) => void): this; + once(event: 'desktop-capturer-get-sources', listener: (event: Event) => void): this; + addListener(event: 'desktop-capturer-get-sources', listener: (event: Event) => void): this; + removeListener(event: 'desktop-capturer-get-sources', listener: (event: Event) => void): this; /** * Emitted when webContents is destroyed. */ @@ -5716,6 +6198,13 @@ declare namespace Electron { once(event: 'dom-ready', listener: (event: Event) => void): this; addListener(event: 'dom-ready', listener: (event: Event) => void): this; removeListener(event: 'dom-ready', listener: (event: Event) => void): this; + /** + * Emitted when the window enters a full-screen state triggered by HTML API. + */ + on(event: 'enter-html-full-screen', listener: Function): this; + once(event: 'enter-html-full-screen', listener: Function): this; + addListener(event: 'enter-html-full-screen', listener: Function): this; + removeListener(event: 'enter-html-full-screen', listener: Function): this; /** * Emitted when a result is available for [webContents.findInPage] request. */ @@ -5727,6 +6216,45 @@ declare namespace Electron { result: Result) => void): this; removeListener(event: 'found-in-page', listener: (event: Event, result: Result) => void): this; + /** + * Emitted when the renderer process sends an asynchronous message via + * ipcRenderer.send(). + */ + on(event: 'ipc-message', listener: (event: Event, + channel: string, + ...args: any[]) => void): this; + once(event: 'ipc-message', listener: (event: Event, + channel: string, + ...args: any[]) => void): this; + addListener(event: 'ipc-message', listener: (event: Event, + channel: string, + ...args: any[]) => void): this; + removeListener(event: 'ipc-message', listener: (event: Event, + channel: string, + ...args: any[]) => void): this; + /** + * Emitted when the renderer process sends a synchronous message via + * ipcRenderer.sendSync(). + */ + on(event: 'ipc-message-sync', listener: (event: Event, + channel: string, + ...args: any[]) => void): this; + once(event: 'ipc-message-sync', listener: (event: Event, + channel: string, + ...args: any[]) => void): this; + addListener(event: 'ipc-message-sync', listener: (event: Event, + channel: string, + ...args: any[]) => void): this; + removeListener(event: 'ipc-message-sync', listener: (event: Event, + channel: string, + ...args: any[]) => void): this; + /** + * Emitted when the window leaves a full-screen state triggered by HTML API. + */ + on(event: 'leave-html-full-screen', listener: Function): this; + once(event: 'leave-html-full-screen', listener: Function): this; + addListener(event: 'leave-html-full-screen', listener: Function): this; + removeListener(event: 'leave-html-full-screen', listener: Function): this; /** * Emitted when webContents wants to do basic auth. The usage is the same with the * login event of app. @@ -5941,6 +6469,21 @@ declare namespace Electron { removeListener(event: 'plugin-crashed', listener: (event: Event, name: string, version: string) => void): this; + /** + * Emitted when the preload script preloadPath throws an unhandled exception error. + */ + on(event: 'preload-error', listener: (event: Event, + preloadPath: string, + error: Error) => void): this; + once(event: 'preload-error', listener: (event: Event, + preloadPath: string, + error: Error) => void): this; + addListener(event: 'preload-error', listener: (event: Event, + preloadPath: string, + error: Error) => void): this; + removeListener(event: 'preload-error', listener: (event: Event, + preloadPath: string, + error: Error) => void): this; /** * Emitted when remote.getBuiltin() is called in the renderer process. Calling * event.preventDefault() will prevent the module from being returned. Custom value @@ -6205,16 +6748,23 @@ declare namespace Electron { canGoBack(): boolean; canGoForward(): boolean; canGoToOffset(offset: number): boolean; + /** + * Captures a snapshot of the page within rect. Omitting rect will capture the + * whole visible page. + */ + capturePage(rect?: Rectangle): Promise; /** * Captures a snapshot of the page within rect. Upon completion callback will be * called with callback(image). The image is an instance of NativeImage that stores * data of the snapshot. Omitting rect will capture the whole visible page. + * Deprecated Soon */ capturePage(rect: Rectangle, callback: (image: NativeImage) => void): void; /** * Captures a snapshot of the page within rect. Upon completion callback will be * called with callback(image). The image is an instance of NativeImage that stores * data of the snapshot. Omitting rect will capture the whole visible page. + * Deprecated Soon */ capturePage(callback: (image: NativeImage) => void): void; /** @@ -6261,12 +6811,15 @@ declare namespace Electron { /** * Evaluates code in page. In the browser window some HTML APIs like * requestFullScreen can only be invoked by a gesture from the user. Setting - * userGesture to true will remove this limitation. If the result of the executed - * code is a promise the callback result will be the resolved value of the promise. - * We recommend that you use the returned Promise to handle code that results in a - * Promise. + * userGesture to true will remove this limitation. Deprecated Soon */ executeJavaScript(code: string, userGesture?: boolean, callback?: (result: any) => void): Promise; + /** + * Evaluates code in page. In the browser window some HTML APIs like + * requestFullScreen can only be invoked by a gesture from the user. Setting + * userGesture to true will remove this limitation. + */ + executeJavaScript(code: string, userGesture?: boolean): Promise; /** * Starts a request to find all matches for the text in the web page. The result of * the request can be obtained by subscribing to found-in-page event. @@ -6288,16 +6841,8 @@ declare namespace Electron { getURL(): string; getUserAgent(): string; getWebRTCIPHandlingPolicy(): string; - /** - * 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; + getZoomFactor(): number; + getZoomLevel(): number; /** * Makes the browser go back a web page. */ @@ -6314,11 +6859,6 @@ declare namespace Electron { * Navigates to the specified offset from the "current entry". */ goToOffset(offset: number): void; - /** - * Checks if any ServiceWorker is registered and returns a boolean as response to - * callback. - */ - hasServiceWorker(callback: (hasWorker: boolean) => void): void; /** * Injects CSS into the current web page. */ @@ -6335,6 +6875,10 @@ declare namespace Electron { * Opens the developer tools for the service worker context. */ inspectServiceWorker(): void; + /** + * Opens the developer tools for the shared worker context. + */ + inspectSharedWorker(): void; /** * Schedules a full repaint of the window this web contents is in. If offscreen * rendering is enabled invalidates the frame and generates a new one through the @@ -6358,13 +6902,13 @@ 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, options?: LoadFileOptions): void; + loadFile(filePath: string, options?: LoadFileOptions): Promise; /** * 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 * header to achieve it. */ - loadURL(url: string, options?: LoadURLOptions): void; + loadURL(url: string, options?: LoadURLOptions): Promise; /** * Opens the devtools. When contents is a tag, the mode would be detach * by default, explicitly passing an empty mode can force using last used dock @@ -6387,13 +6931,18 @@ declare namespace Electron { * Use page-break-before: always; CSS style to force to print to a new page. */ print(options?: PrintOptions, callback?: (success: boolean) => void): void; + /** + * Prints window's web page as PDF with Chromium's preview printing custom + * settings. The landscape will be ignored if @page CSS at-rule is used in the web + * page. By default, an empty options will be regarded as: Use page-break-before: + * always; CSS style to force to print to a new page. An example of + * webContents.printToPDF: + */ + printToPDF(options: PrintToPDFOptions): Promise; /** * Prints window's web page as PDF with Chromium's preview printing custom * settings. The callback will be called with callback(error, data) on completion. - * The data is a Buffer that contains the generated PDF data. The landscape will be - * ignored if @page CSS at-rule is used in the web page. By default, an empty - * options will be regarded as: Use page-break-before: always; CSS style to force - * to print to a new page. An example of webContents.printToPDF: + * The data is a Buffer that contains the generated PDF data. Deprecated Soon */ printToPDF(options: PrintToPDFOptions, callback: (error: Error, data: Buffer) => void): void; /** @@ -6420,7 +6969,7 @@ declare namespace Electron { * Executes the editing command replaceMisspelling in web page. */ replaceMisspelling(text: string): void; - savePage(fullPath: string, saveType: 'HTMLOnly' | 'HTMLComplete' | 'MHTML', callback: (error: Error) => void): boolean; + savePage(fullPath: string, saveType: 'HTMLOnly' | 'HTMLComplete' | 'MHTML'): Promise; /** * Executes the editing command selectAll in web page. */ @@ -6441,6 +6990,16 @@ declare namespace Electron { * object also have following properties: */ sendInputEvent(event: Event): void; + /** + * Send an asynchronous message to a specific frame in a renderer process via + * channel. Arguments will be serialized as JSON internally and as such no + * functions or prototype chains will be included. The renderer process can handle + * the message by listening to channel with the ipcRenderer module. If you want to + * get the frameId of a given renderer context you should use the + * webFrame.routingId value. E.g. You can also read frameId from all incoming IPC + * messages in the main process. + */ + sendToFrame(frameId: number, channel: string, ...args: any[]): void; /** * Mute the audio on the current web page. */ @@ -6539,12 +7098,6 @@ declare namespace Electron { * Executes the editing command undo in web page. */ undo(): void; - /** - * Unregisters any ServiceWorker if present and returns a boolean as response to - * callback when the JS promise is fulfilled or false when the JS promise is - * rejected. - */ - unregisterServiceWorker(callback: (success: boolean) => void): void; /** * Executes the editing command unselect in web page. */ @@ -6574,11 +7127,22 @@ declare namespace Electron { * requestFullScreen can only be invoked by a gesture from the user. Setting * userGesture to true will remove this limitation. */ + executeJavaScript(code: string, userGesture?: boolean): Promise; + /** + * Evaluates code in page. In the browser window some HTML APIs like + * requestFullScreen can only be invoked by a gesture from the user. Setting + * userGesture to true will remove this limitation. Deprecated Soon + */ executeJavaScript(code: string, userGesture?: boolean, callback?: (result: any) => void): Promise; /** - * Work like executeJavaScript but evaluates scripts in an isolated context. + * Works like executeJavaScript but evaluates scripts in an isolated context. + * Deprecated Soon */ - executeJavaScriptInIsolatedWorld(worldId: number, scripts: WebSource[], userGesture?: boolean, callback?: (result: any) => void): void; + executeJavaScriptInIsolatedWorld(worldId: number, scripts: WebSource[], userGesture?: boolean, callback?: (result: any) => void): Promise; + /** + * Works like executeJavaScript but evaluates scripts in an isolated context. + */ + executeJavaScriptInIsolatedWorld(worldId: number, scripts: WebSource[], userGesture?: boolean): Promise; findFrameByName(name: string): WebFrame; findFrameByRoutingId(routingId: number): WebFrame; getFrameForSelector(selector: string): WebFrame; @@ -6589,22 +7153,14 @@ declare namespace Electron { getResourceUsage(): ResourceUsage; getZoomFactor(): number; getZoomLevel(): number; + /** + * Inserts css as a style sheet in the document. + */ + insertCSS(css: string): void; /** * Inserts text to the focused element. */ insertText(text: string): void; - /** - * Resources will be loaded from this scheme regardless of the current page's - * Content Security Policy. - */ - registerURLSchemeAsBypassingCSP(scheme: string): void; - /** - * Registers the scheme as secure, bypasses content security policy for resources, - * allows registering ServiceWorker and supports fetch API. Specify an option with - * the value of false to omit it from the registration. An example of registering a - * privileged scheme, without bypassing Content Security Policy: - */ - registerURLSchemeAsPrivileged(scheme: string, options?: RegisterURLSchemeAsPrivilegedOptions): void; /** * Set the content security policy of the isolated world. */ @@ -6613,6 +7169,11 @@ declare namespace Electron { * Set the name of the isolated world. Useful in devtools. */ setIsolatedWorldHumanReadableName(worldId: number, name: string): void; + /** + * Set the security origin, content security policy and name of the isolated world. + * Note: If the csp is specified, then the securityOrigin also has to be specified. + */ + setIsolatedWorldInfo(worldId: number, info: Info): void; /** * Set the security origin of the isolated world. */ @@ -6623,10 +7184,12 @@ declare namespace Electron { setLayoutZoomLevelLimits(minimumLevel: number, maximumLevel: number): void; /** * Sets a provider for spell checking in input fields and text areas. The provider - * must be an object that has a spellCheck method that returns whether the word - * passed is correctly spelled. An example of using node-spellchecker as provider: + * must be an object that has a spellCheck method that accepts an array of + * individual words for spellchecking. The spellCheck function runs asynchronously + * and calls the callback function with an array of misspelt words when complete. + * An example of using node-spellchecker as provider: */ - setSpellCheckProvider(language: string, autoCorrectWord: boolean, provider: Provider): void; + setSpellCheckProvider(language: string, provider: Provider): void; /** * Sets the maximum and minimum pinch-to-zoom level. */ @@ -6685,90 +7248,90 @@ declare namespace Electron { * The listener will be called with listener(details) when a server initiated * redirect is about to occur. */ - onBeforeRedirect(listener: (details: OnBeforeRedirectDetails) => void): void; + onBeforeRedirect(listener: ((details: OnBeforeRedirectDetails) => void) | (null)): void; /** * The listener will be called with listener(details) when a server initiated * redirect is about to occur. */ - onBeforeRedirect(filter: OnBeforeRedirectFilter, listener: (details: OnBeforeRedirectDetails) => void): void; + onBeforeRedirect(filter: OnBeforeRedirectFilter, listener: ((details: OnBeforeRedirectDetails) => void) | (null)): void; /** * The listener will be called with listener(details, callback) when a request is * about to occur. The uploadData is an array of UploadData objects. The callback - * has to be called with an response object. + * has to be called with an response object. Some examples of valid urls: */ - onBeforeRequest(listener: (details: OnBeforeRequestDetails, callback: (response: Response) => void) => void): void; + onBeforeRequest(listener: ((details: OnBeforeRequestDetails, callback: (response: Response) => void) => void) | (null)): void; /** * The listener will be called with listener(details, callback) when a request is * about to occur. The uploadData is an array of UploadData objects. The callback - * has to be called with an response object. + * has to be called with an response object. Some examples of valid urls: */ - onBeforeRequest(filter: OnBeforeRequestFilter, listener: (details: OnBeforeRequestDetails, callback: (response: Response) => void) => void): void; + onBeforeRequest(filter: OnBeforeRequestFilter, listener: ((details: OnBeforeRequestDetails, callback: (response: Response) => void) => void) | (null)): void; /** * The listener will be called with listener(details, callback) before sending an * HTTP request, once the request headers are available. This may occur after a TCP * connection is made to the server, but before any http data is sent. The callback * has to be called with an response object. */ - onBeforeSendHeaders(filter: OnBeforeSendHeadersFilter, listener: (details: OnBeforeSendHeadersDetails, callback: (response: OnBeforeSendHeadersResponse) => void) => void): void; + onBeforeSendHeaders(filter: OnBeforeSendHeadersFilter, listener: ((details: OnBeforeSendHeadersDetails, callback: (response: OnBeforeSendHeadersResponse) => void) => void) | (null)): void; /** * The listener will be called with listener(details, callback) before sending an * HTTP request, once the request headers are available. This may occur after a TCP * connection is made to the server, but before any http data is sent. The callback * has to be called with an response object. */ - onBeforeSendHeaders(listener: (details: OnBeforeSendHeadersDetails, callback: (response: OnBeforeSendHeadersResponse) => void) => void): void; + onBeforeSendHeaders(listener: ((details: OnBeforeSendHeadersDetails, callback: (response: OnBeforeSendHeadersResponse) => void) => void) | (null)): void; /** * The listener will be called with listener(details) when a request is completed. */ - onCompleted(filter: OnCompletedFilter, listener: (details: OnCompletedDetails) => void): void; + onCompleted(filter: OnCompletedFilter, listener: ((details: OnCompletedDetails) => void) | (null)): void; /** * The listener will be called with listener(details) when a request is completed. */ - onCompleted(listener: (details: OnCompletedDetails) => void): void; + onCompleted(listener: ((details: OnCompletedDetails) => void) | (null)): void; /** * The listener will be called with listener(details) when an error occurs. */ - onErrorOccurred(listener: (details: OnErrorOccurredDetails) => void): void; + onErrorOccurred(listener: ((details: OnErrorOccurredDetails) => void) | (null)): void; /** * The listener will be called with listener(details) when an error occurs. */ - onErrorOccurred(filter: OnErrorOccurredFilter, listener: (details: OnErrorOccurredDetails) => void): void; + onErrorOccurred(filter: OnErrorOccurredFilter, listener: ((details: OnErrorOccurredDetails) => void) | (null)): void; /** * The listener will be called with listener(details, callback) when HTTP response * headers of a request have been received. The callback has to be called with an * response object. */ - onHeadersReceived(filter: OnHeadersReceivedFilter, listener: (details: OnHeadersReceivedDetails, callback: (response: OnHeadersReceivedResponse) => void) => void): void; + onHeadersReceived(filter: OnHeadersReceivedFilter, listener: ((details: OnHeadersReceivedDetails, callback: (response: OnHeadersReceivedResponse) => void) => void) | (null)): void; /** * The listener will be called with listener(details, callback) when HTTP response * headers of a request have been received. The callback has to be called with an * response object. */ - onHeadersReceived(listener: (details: OnHeadersReceivedDetails, callback: (response: OnHeadersReceivedResponse) => void) => void): void; + onHeadersReceived(listener: ((details: OnHeadersReceivedDetails, callback: (response: OnHeadersReceivedResponse) => void) => void) | (null)): void; /** * The listener will be called with listener(details) when first byte of the * response body is received. For HTTP requests, this means that the status line * and response headers are available. */ - onResponseStarted(listener: (details: OnResponseStartedDetails) => void): void; + onResponseStarted(listener: ((details: OnResponseStartedDetails) => void) | (null)): void; /** * The listener will be called with listener(details) when first byte of the * response body is received. For HTTP requests, this means that the status line * and response headers are available. */ - onResponseStarted(filter: OnResponseStartedFilter, listener: (details: OnResponseStartedDetails) => void): void; + onResponseStarted(filter: OnResponseStartedFilter, listener: ((details: OnResponseStartedDetails) => void) | (null)): void; /** * The listener will be called with listener(details) just before a request is * going to be sent to the server, modifications of previous onBeforeSendHeaders * response are visible by the time this listener is fired. */ - onSendHeaders(filter: OnSendHeadersFilter, listener: (details: OnSendHeadersDetails) => void): void; + onSendHeaders(filter: OnSendHeadersFilter, listener: ((details: OnSendHeadersDetails) => void) | (null)): void; /** * The listener will be called with listener(details) just before a request is * going to be sent to the server, modifications of previous onBeforeSendHeaders * response are visible by the time this listener is fired. */ - onSendHeaders(listener: (details: OnSendHeadersDetails) => void): void; + onSendHeaders(listener: ((details: OnSendHeadersDetails) => void) | (null)): void; } interface WebSource { @@ -6963,15 +7526,24 @@ declare namespace Electron { canGoForward(): boolean; canGoToOffset(offset: number): boolean; /** - * Captures a snapshot of the webview's page. Same as - * webContents.capturePage([rect, ]callback). + * Captures a snapshot of the page within rect. Upon completion callback will be + * called with callback(image). The image is an instance of NativeImage that stores + * data of the snapshot. Omitting rect will capture the whole visible page. + * Deprecated Soon */ capturePage(callback: (image: NativeImage) => void): void; /** - * Captures a snapshot of the webview's page. Same as - * webContents.capturePage([rect, ]callback). + * Captures a snapshot of the page within rect. Upon completion callback will be + * called with callback(image). The image is an instance of NativeImage that stores + * data of the snapshot. Omitting rect will capture the whole visible page. + * Deprecated Soon */ capturePage(rect: Rectangle, callback: (image: NativeImage) => void): void; + /** + * Captures a snapshot of the page within rect. Omitting rect will capture the + * whole visible page. + */ + capturePage(rect?: Rectangle): Promise; /** * Clears the navigation history. */ @@ -6996,12 +7568,18 @@ declare namespace Electron { * 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 + * action, can take advantage of this option for automation. Deprecated Soon + */ + executeJavaScript(code: string, userGesture?: boolean, callback?: (result: any) => void): Promise; /** * 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 * action, can take advantage of this option for automation. */ - executeJavaScript(code: string, userGesture?: boolean, callback?: (result: any) => void): void; + executeJavaScript(code: string, userGesture?: boolean): Promise; /** * Starts a request to find all matches for the text in the web page. The result of * the request can be obtained by subscribing to found-in-page event. @@ -7015,16 +7593,9 @@ declare namespace Electron { * 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; + getWebContentsId(): number; + getZoomFactor(): number; + getZoomLevel(): number; /** * Makes the guest page go back. */ @@ -7057,6 +7628,10 @@ declare namespace Electron { * Opens the DevTools for the service worker context present in the guest page. */ inspectServiceWorker(): void; + /** + * Opens the DevTools for the shared worker context present in the guest page. + */ + inspectSharedWorker(): void; isAudioMuted(): boolean; isCrashed(): boolean; isCurrentlyAudible(): boolean; @@ -7069,7 +7644,7 @@ declare namespace Electron { * Loads the url in the webview, the url must contain the protocol prefix, e.g. the * http:// or file://. */ - loadURL(url: string, options?: LoadURLOptions): void; + loadURL(url: string, options?: LoadURLOptions): Promise; /** * Opens a DevTools window for guest page. */ @@ -7088,9 +7663,13 @@ declare namespace Electron { print(options?: PrintOptions): void; /** * Prints webview's web page as PDF, Same as webContents.printToPDF(options, - * callback). + * callback). Deprecated Soon */ printToPDF(options: PrintToPDFOptions, callback: (error: Error, data: Buffer) => void): void; + /** + * Prints webview's web page as PDF, Same as webContents.printToPDF(options). + */ + printToPDF(options: PrintToPDFOptions): Promise; /** * Executes editing command redo in page. */ @@ -7180,14 +7759,6 @@ declare namespace Electron { * windows. Popups are disabled by default. */ // allowpopups?: string; ### VSCODE CHANGE (https://github.com/electron/electron/blob/master/docs/tutorial/security.md) ### - /** - * When this attribute is present the webview container will automatically resize - * within the bounds specified by the attributes minwidth, minheight, maxwidth, and - * maxheight. These constraints do not impact the webview unless autosize is - * enabled. When autosize is enabled, the webview container size cannot be less - * than the minimum values or greater than the maximum. - */ - autosize?: string; /** * A list of strings which specifies the blink features to be disabled separated by * ,. The full list of supported feature strings can be found in the @@ -7207,7 +7778,7 @@ declare namespace Electron { 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. + * the remote module. The remote module is available by default. */ enableremotemodule?: string; /** @@ -7220,6 +7791,13 @@ declare namespace Electron { * system resources. Node integration is disabled by default in the guest page. */ nodeintegration?: string; + /** + * Experimental option for enabling NodeJS support in sub-frames such as iframes + * inside the webview. All your preloads will load for every iframe, you can use + * process.isMainFrame to determine if you are in the main frame or not. This + * option is disabled by default in the guest page. + */ + nodeintegrationinsubframes?: string; /** * Sets the session used by the page. If partition starts with persist:, the page * will use a persistent session available to all pages in the app with the same @@ -7281,14 +7859,23 @@ declare namespace Electron { * Copyright information. */ copyright?: string; + /** + * The app's build version number. + */ + version?: string; /** * Credit information. */ credits?: string; /** - * The app's build version number. + * The app's website. */ - version?: string; + website?: string; + /** + * Path to the app's icon. Will be shown as 64x64 pixels while retaining aspect + * ratio. + */ + iconPath?: string; } interface AddRepresentationOptions { @@ -7314,6 +7901,24 @@ declare namespace Electron { dataURL?: string; } + interface AnimationSettings { + /** + * Returns true if rich animations should be rendered. Looks at session type (e.g. + * remote desktop) and accessibility settings to give guidance for heavy + * animations. + */ + shouldRenderRichAnimation: boolean; + /** + * Determines on a per-platform basis whether scroll animations (e.g. produced by + * home/end key) should be enabled. + */ + scrollAnimationsEnabledBySystem: boolean; + /** + * Determines whether the user desires reduced motion based on platform APIs. + */ + prefersReducedMotion: boolean; + } + interface AppDetailsOptions { /** * Window's . It has to be set, otherwise the other options will have no effect. @@ -7357,6 +7962,16 @@ declare namespace Electron { * by default. */ height: boolean; + /** + * If true, the view's x position and width will grow and shrink proportionly with + * the window. false by default. + */ + horizontal: boolean; + /** + * If true, the view's y position and height will grow and shrink proportinaly with + * the window. false by default. + */ + vertical: boolean; } interface BitmapOptions { @@ -7475,7 +8090,9 @@ declare namespace Electron { */ kiosk?: boolean; /** - * Default window title. Default is "Electron". + * Default window title. Default is "Electron". If the HTML tag is defined + * in the HTML file loaded by loadURL(), this property will be + * ignored. */ title?: string; /** @@ -7520,8 +8137,8 @@ declare namespace Electron { enableLargerThanScreen?: boolean; /** * Window's background color as a hexadecimal value, like #66CD00 or #FFF or - * #80FFFFFF (alpha is supported if transparent is set to true). Default is #FFF - * (white). + * #80FFFFFF (alpha in #AARRGGBB format is supported if transparent is set to + * true). Default is #FFF (white). */ backgroundColor?: string; /** @@ -7633,15 +8250,24 @@ declare namespace Electron { interface CommandLine { /** * Append a switch (with optional value) to Chromium's command line. Note: This - * will not affect process.argv, and is mainly used by developers to control some - * low-level Chromium behaviors. + * will not affect process.argv. The intended usage of this function is to control + * Chromium's behavior. */ appendSwitch: (the_switch: string, value?: string) => void; /** * Append an argument to Chromium's command line. The argument will be quoted - * correctly. Note: This will not affect process.argv. + * correctly. Switches will precede arguments regardless of appending order. If + * you're appending an argument like --switch=value, consider using + * appendSwitch('switch', 'value') instead. Note: This will not affect + * process.argv. The intended usage of this function is to control Chromium's + * behavior. */ appendArgument: (value: string) => void; + hasSwitch: (the_switch: string) => boolean; + /** + * Note: When the switch is not present or has no value, it returns empty string. + */ + getSwitchValue: (the_switch: string) => string; } interface Config { @@ -7778,6 +8404,15 @@ declare namespace Electron { crashesDirectory?: string; } + interface CreateFromBitmapOptions { + width: number; + height: number; + /** + * Defaults to 1.0. + */ + scaleFactor?: number; + } + interface CreateFromBufferOptions { /** * Required for bitmap buffers. @@ -7831,7 +8466,7 @@ declare namespace Electron { image?: NativeImage; rtf?: string; /** - * The title of the url at text. + * The title of the URL at text. */ bookmark?: string; } @@ -7850,8 +8485,7 @@ declare namespace Electron { */ value?: string; /** - * 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. + * The domain of the cookie. Empty by default if omitted. */ domain?: string; /** @@ -7915,7 +8549,9 @@ declare namespace Electron { * When critical is passed, the dock icon will bounce until either the application * becomes active or the request is canceled. When informational is passed, the * dock icon will bounce for one second. However, the request remains active until - * either the application becomes active or the request is canceled. + * either the application becomes active or the request is canceled. Nota Bene: + * This method can only be used while the app is not focused; when the app is + * focused it will return -1. */ bounce: (type?: 'critical' | 'informational') => number; /** @@ -7935,15 +8571,13 @@ declare namespace Electron { * Hides the dock icon. */ hide: () => void; - /** - * Shows the dock icon. - */ - show: () => void; + show: () => Promise; isVisible: () => boolean; /** * Sets the application's dock menu. */ setMenu: (menu: Menu) => void; + getMenu: () => (Menu) | (null); /** * Sets the image associated with this dock icon. */ @@ -8094,6 +8728,21 @@ declare namespace Electron { password: string; } + interface Info { + /** + * Security origin for the isolated world. + */ + securityOrigin?: string; + /** + * Content Security Policy for the isolated world. + */ + csp?: string; + /** + * Name for isolated world. Useful in devtools. + */ + name?: string; + } + interface Input { /** * Either keyUp or keyDown. @@ -8288,12 +8937,18 @@ declare namespace Electron { * Will be called with click(menuItem, browserWindow, event) when the menu item is * clicked. */ - click?: (menuItem: MenuItem, browserWindow: BrowserWindow, event: Event) => void; + click?: (menuItem: MenuItem, browserWindow: BrowserWindow, event: KeyboardEvent) => void; /** - * Define the action of the menu item, when specified the click property will be - * ignored. See . + * Can be undo, redo, cut, copy, paste, pasteAndMatchStyle, delete, selectAll, + * reload, forceReload, toggleDevTools, resetZoom, zoomIn, zoomOut, + * togglefullscreen, window, minimize, close, help, about, services, hide, + * hideOthers, unhide, quit, startSpeaking, stopSpeaking, close, minimize, zoom, + * front, appMenu, fileMenu, editMenu, viewMenu, recentDocuments, toggleTabBar, + * selectNextTab, selectPreviousTab, mergeAllWindows, clearRecentDocuments, + * moveTabToNewWindow or windowMenu Define the action of the menu item, when + * specified the click property will be ignored. See . */ - role?: string; + role?: ('undo' | 'redo' | 'cut' | 'copy' | 'paste' | 'pasteAndMatchStyle' | 'delete' | 'selectAll' | 'reload' | 'forceReload' | 'toggleDevTools' | 'resetZoom' | 'zoomIn' | 'zoomOut' | 'togglefullscreen' | 'window' | 'minimize' | 'close' | 'help' | 'about' | 'services' | 'hide' | 'hideOthers' | 'unhide' | 'quit' | 'startSpeaking' | 'stopSpeaking' | 'close' | 'minimize' | 'zoom' | 'front' | 'appMenu' | 'fileMenu' | 'editMenu' | 'viewMenu' | 'recentDocuments' | 'toggleTabBar' | 'selectNextTab' | 'selectPreviousTab' | 'mergeAllWindows' | 'clearRecentDocuments' | 'moveTabToNewWindow' | 'windowMenu'); /** * Can be normal, separator, submenu, checkbox or radio. */ @@ -8306,6 +8961,11 @@ declare namespace Electron { * If false, the menu item will be greyed out and unclickable. */ enabled?: boolean; + /** + * default is true, and when false will prevent the accelerator from triggering the + * item if the item is not visible`. + */ + acceleratorWorksWhenHidden?: boolean; /** * If false, the menu item will be entirely hidden. */ @@ -8421,6 +9081,82 @@ declare namespace Electron { normalizeAccessKeys?: boolean; } + interface MessageBoxReturnValue { + /** + * The index of the clicked button. + */ + response: number; + /** + * The checked state of the checkbox if checkboxLabel was set. Otherwise false. + */ + checkboxChecked: boolean; + } + + interface MessageBoxSyncOptions { + /** + * Can be "none", "info", "error", "question" or "warning". On Windows, "question" + * displays the same icon as "info", unless you set an icon using the "icon" + * option. On macOS, both "warning" and "error" display the same warning icon. + */ + type?: string; + /** + * Array of texts for buttons. On Windows, an empty array will result in one button + * labeled "OK". + */ + buttons?: string[]; + /** + * Index of the button in the buttons array which will be selected by default when + * the message box opens. + */ + defaultId?: number; + /** + * Title of the message box, some platforms will not show it. + */ + title?: string; + /** + * Content of the message box. + */ + message: string; + /** + * Extra information of the message. + */ + detail?: string; + /** + * If provided, the message box will include a checkbox with the given label. The + * checkbox state can be inspected only when using callback. + */ + checkboxLabel?: string; + /** + * Initial checked state of the checkbox. false by default. + */ + checkboxChecked?: boolean; + icon?: (NativeImage) | (string); + /** + * 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. + */ + cancelId?: number; + /** + * On Windows Electron will try to figure out which one of the buttons are common + * buttons (like "Cancel" or "Yes"), and show the others as command links in the + * dialog. This can make the dialog appear in the style of modern Windows apps. If + * you don't like this behavior, you can set noLink to true. + */ + noLink?: boolean; + /** + * Normalize the keyboard access keys across platforms. Default is false. Enabling + * this assumes & is used in the button labels for the placement of the keyboard + * shortcut access key and labels will be converted so they work correctly on each + * platform, & characters are removed on macOS, converted to _ on Linux, and left + * untouched on Windows. For example, a button label of Vie&w will be converted to + * Vie_w on Linux and View on macOS and can be selected via Alt-W on Windows and + * Linux. + */ + normalizeAccessKeys?: boolean; + } + interface NewWindowEvent extends Event { url: string; frameName: string; @@ -8488,6 +9224,7 @@ declare namespace Electron { method: string; webContentsId?: number; resourceType: string; + referrer: string; timestamp: number; redirectURL: string; statusCode: number; @@ -8513,6 +9250,7 @@ declare namespace Electron { method: string; webContentsId?: number; resourceType: string; + referrer: string; timestamp: number; uploadData: UploadData[]; } @@ -8531,6 +9269,7 @@ declare namespace Electron { method: string; webContentsId?: number; resourceType: string; + referrer: string; timestamp: number; requestHeaders: RequestHeaders; } @@ -8579,6 +9318,7 @@ declare namespace Electron { method: string; webContentsId?: number; resourceType: string; + referrer: string; timestamp: number; fromCache: boolean; /** @@ -8601,6 +9341,7 @@ declare namespace Electron { method: string; webContentsId?: number; resourceType: string; + referrer: string; timestamp: number; statusLine: string; statusCode: number; @@ -8634,6 +9375,7 @@ declare namespace Electron { method: string; webContentsId?: number; resourceType: string; + referrer: string; timestamp: number; responseHeaders: ResponseHeaders; /** @@ -8658,6 +9400,7 @@ declare namespace Electron { method: string; webContentsId?: number; resourceType: string; + referrer: string; timestamp: number; requestHeaders: RequestHeaders; } @@ -8677,6 +9420,11 @@ declare namespace Electron { * back. In detach mode it's not. */ mode: ('right' | 'bottom' | 'undocked' | 'detach'); + /** + * Whether to bring the opened devtools window to the foreground. The default is + * true. + */ + activate?: boolean; } interface OpenDialogOptions { @@ -8703,6 +9451,48 @@ declare namespace Electron { securityScopedBookmarks?: boolean; } + interface OpenDialogReturnValue { + /** + * whether or not the dialog was canceled. + */ + canceled: boolean; + /** + * An array of file paths chosen by the user. If the dialog is cancelled this will + * be an empty array. + */ + filePaths?: string[]; + /** + * An array matching the filePaths array of base64 encoded strings which contains + * security scoped bookmark data. securityScopedBookmarks must be enabled for this + * to be populated. + */ + bookmarks?: string[]; + } + + interface OpenDialogSyncOptions { + title?: string; + defaultPath?: string; + /** + * Custom label for the confirmation button, when left empty the default label will + * be used. + */ + buttonLabel?: string; + filters?: FileFilter[]; + /** + * Contains which features the dialog should use. The following values are + * supported: + */ + properties?: Array<'openFile' | 'openDirectory' | 'multiSelections' | 'showHiddenFiles' | 'createDirectory' | 'promptToCreate' | 'noResolveAliases' | 'treatPackageAsDirectory'>; + /** + * Message to display above input boxes. + */ + message?: string; + /** + * Create when packaged for the Mac App Store. + */ + securityScopedBookmarks?: boolean; + } + interface OpenExternalOptions { /** * true to bring the opened application to the foreground. The default is true. @@ -8714,6 +9504,17 @@ declare namespace Electron { workingDirectory?: string; } + interface OpenExternalSyncOptions { + /** + * true to bring the opened application to the foreground. The default is true. + */ + activate?: boolean; + /** + * The working directory. + */ + workingDirectory?: string; + } + interface PageFaviconUpdatedEvent extends Event { /** * Array of URLs. @@ -8874,21 +9675,31 @@ declare namespace Electron { landscape?: boolean; } - interface ProcessMemoryInfo { + interface Privileges { /** - * and The amount of memory currently pinned to actual physical RAM in Kilobytes. + * Default false. */ - residentSet: number; + standard?: boolean; /** - * The amount of memory not shared by other processes, such as JS heap or HTML - * content in Kilobytes. + * Default false. */ - private: number; + secure?: boolean; /** - * The amount of memory shared between processes, typically memory consumed by the - * Electron code itself in Kilobytes. + * Default false. */ - shared: number; + bypassCSP?: boolean; + /** + * Default false. + */ + allowServiceWorkers?: boolean; + /** + * Default false. + */ + supportFetchAPI?: boolean; + /** + * Default false. + */ + corsEnabled?: boolean; } interface ProgressBarOptions { @@ -8900,9 +9711,9 @@ declare namespace Electron { interface Provider { /** - * Returns Boolean. + * . */ - spellCheck: (text: string) => void; + spellCheck: (words: string[], callback: (misspeltWords: string[]) => void) => void; } interface ReadBookmark { @@ -8939,13 +9750,6 @@ declare namespace Electron { uploadData: UploadData[]; } - interface RegisterStandardSchemesOptions { - /** - * true to register the scheme as secure. Default false. - */ - secure?: boolean; - } - interface RegisterStreamProtocolRequest { url: string; headers: Headers; @@ -8961,29 +9765,6 @@ declare namespace Electron { uploadData: UploadData[]; } - interface RegisterURLSchemeAsPrivilegedOptions { - /** - * Default true. - */ - secure?: boolean; - /** - * Default true. - */ - bypassCSP?: boolean; - /** - * Default true. - */ - allowServiceWorkers?: boolean; - /** - * Default true. - */ - supportFetchAPI?: boolean; - /** - * Default true. - */ - corsEnabled?: boolean; - } - interface RelaunchOptions { args?: string[]; execPath?: string; @@ -9080,6 +9861,53 @@ declare namespace Electron { securityScopedBookmarks?: boolean; } + interface SaveDialogReturnValue { + /** + * whether or not the dialog was canceled. + */ + canceled: boolean; + /** + * If the dialog is canceled this will be undefined. + */ + filePath?: string; + /** + * Base64 encoded string which contains the security scoped bookmark data for the + * saved file. securityScopedBookmarks must be enabled for this to be present. + */ + bookmark?: string; + } + + interface SaveDialogSyncOptions { + title?: string; + /** + * Absolute directory path, absolute file path, or file name to use by default. + */ + defaultPath?: string; + /** + * Custom label for the confirmation button, when left empty the default label will + * be used. + */ + buttonLabel?: string; + filters?: FileFilter[]; + /** + * Message to display above text fields. + */ + message?: string; + /** + * Custom label for the text displayed in front of the filename text field. + */ + nameFieldLabel?: string; + /** + * Show the tags input box, defaults to true. + */ + showsTagField?: boolean; + /** + * Create a when packaged for the Mac App Store. If this option is enabled and the + * file doesn't already exist a blank file will be created at the chosen path. + */ + securityScopedBookmarks?: boolean; + } + interface Settings { /** * true to open the app at login, false to remove the app as a login item. Defaults @@ -9112,14 +9940,17 @@ declare namespace Electron { types: string[]; /** * The size that the media source thumbnail should be scaled to. Default is 150 x - * 150. + * 150. Set width or height to 0 when you do not need the thumbnails. This will + * save the processing time required for capturing the content of each window and + * screen. */ thumbnailSize?: Size; - } - - interface StartMonitoringOptions { - categoryFilter: string; - traceOptions: string; + /** + * Set to true to enable fetching window icons. The default value is false. When + * false the appIcon property of the sources return null. Same if a source has the + * type screen. + */ + fetchWindowIcons?: boolean; } interface SystemMemoryInfo { @@ -9487,7 +10318,7 @@ declare namespace Electron { */ devTools?: boolean; /** - * Whether node integration is enabled. Default is true. + * Whether node integration is enabled. Default is false. */ nodeIntegration?: boolean; /** @@ -9495,6 +10326,12 @@ declare namespace Electron { * this can be found in . */ nodeIntegrationInWorker?: boolean; + /** + * Experimental option for enabling Node.js support in sub-frames such as iframes + * and child windows. All your preloads will load for every iframe, you can use + * process.isMainFrame to determine if you are in the main frame or not. + */ + nodeIntegrationInSubFrames?: boolean; /** * Specifies a script that will be loaded before other scripts run in the page. * This script will always have access to node APIs no matter whether node @@ -9571,10 +10408,6 @@ declare namespace Electron { * Enables WebGL support. Default is true. */ webgl?: boolean; - /** - * Enables WebAudio support. Default is true. - */ - webaudio?: boolean; /** * Whether plugins should be enabled. Default is false. */ @@ -9642,19 +10475,17 @@ declare namespace Electron { */ contextIsolation?: boolean; /** - * Whether to use native window.open(). If set to true, the webPreferences of child - * window will always be the same with parent window, regardless of the parameters - * passed to window.open(). Defaults to false. This option is currently - * experimental. + * Whether to use native window.open(). Defaults to false. Child windows will + * always have node integration disabled unless nodeIntegrationInSubFrames is true. + * This option is currently experimental. */ 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 - * will-attach-webview event on to strip away the preload script and to validate or - * alter the 's initial settings. + * Whether to enable the . Defaults to false. 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. */ webviewTag?: boolean; /** @@ -9678,6 +10509,17 @@ declare namespace Electron { * Default is false. */ navigateOnDragDrop?: boolean; + /** + * Autoplay policy to apply to content in the window, can be + * no-user-gesture-required, user-gesture-required, + * document-user-activation-required. Defaults to no-user-gesture-required. + */ + autoplayPolicy?: ('no-user-gesture-required' | 'user-gesture-required' | 'document-user-activation-required'); + /** + * Whether to prevent the window from resizing when entering HTML Fullscreen. + * Default is false. + */ + disableHtmlFullscreenWindowResize?: boolean; } interface DefaultFontFamily { @@ -9750,7 +10592,6 @@ declare namespace NodeJS { // addListener(event: 'loaded', listener: Function): this; // removeListener(event: 'loaded', listener: Function): this; // ### END VSCODE MODIFICATION ### - /** * Causes the main thread of the current process crash. */ @@ -9777,12 +10618,17 @@ declare namespace NodeJS { * private memory is more representative of the actual pre-compression memory usage * of the process on macOS. */ - getProcessMemoryInfo(): Electron.ProcessMemoryInfo; + getProcessMemoryInfo(): Promise; /** * Returns an object giving memory usage statistics about the entire system. Note * that all statistics are reported in Kilobytes. */ getSystemMemoryInfo(): Electron.SystemMemoryInfo; + /** + * Examples: Note: It returns the actual operating system version instead of kernel + * version on macOS unlike os.release(). + */ + getSystemVersion(): string; /** * Causes the main thread of the current process hang. */ @@ -9801,6 +10647,17 @@ declare namespace NodeJS { * this property is true in the main process, otherwise it is undefined. */ defaultApp?: boolean; + /** + * A Boolean that controls whether or not deprecation warnings are printed to + * stderr when formerly callback-based APIs converted to Promises are invoked using + * callbacks. Setting this to true will enable deprecation warnings. + */ + enablePromiseAPIs?: boolean; + /** + * A Boolean, true when the current renderer context is the "main" renderer frame. + * If you want the ID of the current frame you should use webFrame.routingId. + */ + isMainFrame?: boolean; /** * A Boolean. For Mac App Store build, this property is true, for other builds it * is undefined. @@ -9848,7 +10705,7 @@ declare namespace NodeJS { traceProcessWarnings?: boolean; /** * A String representing the current process's type, can be "browser" (i.e. main - * process) or "renderer". + * process), "renderer", or "worker" (i.e. web worker). */ type?: string; /** diff --git a/src/typings/xterm.d.ts b/src/typings/xterm.d.ts index e7b8a15628..e163c25672 100644 --- a/src/typings/xterm.d.ts +++ b/src/typings/xterm.d.ts @@ -82,6 +82,16 @@ declare module 'xterm' { */ drawBoldTextInBrightColors?: boolean; + /** + * The modifier key hold to multiply scroll speed. + */ + fastScrollModifier?: 'alt' | 'ctrl' | 'shift' | undefined; + + /** + * The scroll speed multiplier used for fast scrolling. + */ + fastScrollSensitivity?: number; + /** * The font size used to render text. */ @@ -173,6 +183,11 @@ declare module 'xterm' { */ scrollback?: number; + /** + * The scrolling speed multiplier used for adjusting normal scrolling speed. + */ + scrollSensitivity?: number; + /** * The size of tab stops in the terminal. */ @@ -269,7 +284,7 @@ declare module 'xterm' { /** * A callback that fires when the mouse hovers over a link for a moment. */ - tooltipCallback?: (event: MouseEvent, uri: string) => boolean | void; + tooltipCallback?: (event: MouseEvent, uri: string, location: IViewportRange) => boolean | void; /** * A callback that fires when the mouse leaves a link. Note that this can @@ -352,12 +367,12 @@ declare module 'xterm' { /** * The element containing the terminal. */ - readonly element: HTMLElement; + readonly element: HTMLElement | undefined; /** * The textarea that accepts input for the terminal. */ - readonly textarea: HTMLTextAreaElement; + readonly textarea: HTMLTextAreaElement | undefined; /** * The number of rows in the terminal's viewport. Use @@ -842,6 +857,36 @@ declare module 'xterm' { endRow: number; } + /** + * An object representing a range within the viewport of the terminal. + */ + interface IViewportRange { + /** + * The start cell of the range. + */ + start: IViewportCellPosition; + + /** + * The end cell of the range. + */ + end: IViewportCellPosition; + } + + /** + * An object representing a cell position within the viewport of the terminal. + */ + interface IViewportCellPosition { + /** + * The column of the cell. Note that this is 1-based; the first column is column 1. + */ + col: number; + + /** + * The row of the cell. Note that this is 1-based; the first row is row 1. + */ + row: number; + } + /** * Represents a terminal buffer. */ diff --git a/src/vs/base/browser/contextmenu.ts b/src/vs/base/browser/contextmenu.ts index c6cb3b7a80..184b9c6f1f 100644 --- a/src/vs/base/browser/contextmenu.ts +++ b/src/vs/base/browser/contextmenu.ts @@ -25,6 +25,7 @@ export class ContextSubMenu extends SubmenuAction { export interface IContextMenuDelegate { getAnchor(): HTMLElement | { x: number; y: number; width?: number; height?: number; }; getActions(): ReadonlyArray; + getCheckedActionsRepresentation?(action: IAction): 'radio' | 'checkbox'; getActionViewItem?(action: IAction): IActionViewItem | undefined; getActionsContext?(event?: IContextMenuEvent): any; getKeyBinding?(action: IAction): ResolvedKeybinding | undefined; diff --git a/src/vs/base/browser/fastDomNode.ts b/src/vs/base/browser/fastDomNode.ts index edabb331a6..5e12e5b7c2 100644 --- a/src/vs/base/browser/fastDomNode.ts +++ b/src/vs/base/browser/fastDomNode.ts @@ -18,6 +18,7 @@ export class FastDomNode { private _fontFamily: string; private _fontWeight: string; private _fontSize: number; + private _fontFeatureSettings: string; private _lineHeight: number; private _letterSpacing: number; private _className: string; @@ -38,6 +39,7 @@ export class FastDomNode { this._fontFamily = ''; this._fontWeight = ''; this._fontSize = -1; + this._fontFeatureSettings = ''; this._lineHeight = -1; this._letterSpacing = -100; this._className = ''; @@ -135,6 +137,14 @@ export class FastDomNode { this.domNode.style.fontSize = this._fontSize + 'px'; } + public setFontFeatureSettings(fontFeatureSettings: string): void { + if (this._fontFeatureSettings === fontFeatureSettings) { + return; + } + this._fontFeatureSettings = fontFeatureSettings; + this.domNode.style.fontFeatureSettings = this._fontFeatureSettings; + } + public setLineHeight(lineHeight: number): void { if (this._lineHeight === lineHeight) { return; diff --git a/src/vs/base/browser/mouseEvent.ts b/src/vs/base/browser/mouseEvent.ts index f3e0496c56..31e2099671 100644 --- a/src/vs/base/browser/mouseEvent.ts +++ b/src/vs/base/browser/mouseEvent.ts @@ -152,8 +152,16 @@ export class StandardWheelEvent { this.deltaX = deltaX; if (e) { - if (e.type === 'wheel') { + // Old (deprecated) wheel events + let e1 = e; + let e2 = e; + // vertical delta scroll + if (typeof e1.wheelDeltaY !== 'undefined') { + this.deltaY = e1.wheelDeltaY / 120; + } else if (typeof e2.VERTICAL_AXIS !== 'undefined' && e2.axis === e2.VERTICAL_AXIS) { + this.deltaY = -e2.detail / 3; + } else if (e.type === 'wheel') { // Modern wheel event // https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent const ev = e; @@ -161,39 +169,36 @@ export class StandardWheelEvent { if (ev.deltaMode === ev.DOM_DELTA_LINE) { // the deltas are expressed in lines this.deltaY = -e.deltaY; - this.deltaX = -e.deltaX; } else { this.deltaY = -e.deltaY / 40; + } + } + + // horizontal delta scroll + if (typeof e1.wheelDeltaX !== 'undefined') { + if (browser.isSafari && platform.isWindows) { + this.deltaX = - (e1.wheelDeltaX / 120); + } else { + this.deltaX = e1.wheelDeltaX / 120; + } + } else if (typeof e2.HORIZONTAL_AXIS !== 'undefined' && e2.axis === e2.HORIZONTAL_AXIS) { + this.deltaX = -e.detail / 3; + } else if (e.type === 'wheel') { + // Modern wheel event + // https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent + const ev = e; + + if (ev.deltaMode === ev.DOM_DELTA_LINE) { + // the deltas are expressed in lines + this.deltaX = -e.deltaX; + } else { this.deltaX = -e.deltaX / 40; } + } - } else { - // Old (deprecated) wheel events - let e1 = e; - let e2 = e; - - // vertical delta scroll - if (typeof e1.wheelDeltaY !== 'undefined') { - this.deltaY = e1.wheelDeltaY / 120; - } else if (typeof e2.VERTICAL_AXIS !== 'undefined' && e2.axis === e2.VERTICAL_AXIS) { - this.deltaY = -e2.detail / 3; - } - - // horizontal delta scroll - if (typeof e1.wheelDeltaX !== 'undefined') { - if (browser.isSafari && platform.isWindows) { - this.deltaX = - (e1.wheelDeltaX / 120); - } else { - this.deltaX = e1.wheelDeltaX / 120; - } - } else if (typeof e2.HORIZONTAL_AXIS !== 'undefined' && e2.axis === e2.HORIZONTAL_AXIS) { - this.deltaX = -e.detail / 3; - } - - // Assume a vertical scroll if nothing else worked - if (this.deltaY === 0 && this.deltaX === 0 && e.wheelDelta) { - this.deltaY = e.wheelDelta / 120; - } + // Assume a vertical scroll if nothing else worked + if (this.deltaY === 0 && this.deltaX === 0 && e.wheelDelta) { + this.deltaY = e.wheelDelta / 120; } } } diff --git a/src/vs/base/browser/touch.ts b/src/vs/base/browser/touch.ts index ac9e47790d..4ab9f1ad08 100644 --- a/src/vs/base/browser/touch.ts +++ b/src/vs/base/browser/touch.ts @@ -67,7 +67,7 @@ export class Gesture extends Disposable { private static readonly SCROLL_FRICTION = -0.005; private static INSTANCE: Gesture; - private static HOLD_DELAY = 700; + private static readonly HOLD_DELAY = 700; private dispatched = false; private targets: HTMLElement[]; diff --git a/src/vs/base/browser/ui/actionbar/actionbar.css b/src/vs/base/browser/ui/actionbar/actionbar.css index 70979638c9..1d162aa21f 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.css +++ b/src/vs/base/browser/ui/actionbar/actionbar.css @@ -50,12 +50,6 @@ margin-right: 4px; } -.monaco-action-bar .action-label.octicon { - font-size: 15px; - line-height: 35px; - text-align: center; -} - .monaco-action-bar .action-item.disabled .action-label, .monaco-action-bar .action-item.disabled .action-label:hover { opacity: 0.4; diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 32d626f56b..7c29d8a942 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -33,11 +33,12 @@ export interface IBaseActionViewItemOptions { export class BaseActionViewItem extends Disposable implements IActionViewItem { - element?: HTMLElement; + element: HTMLElement | undefined; + _context: any; _action: IAction; - private _actionRunner!: IActionRunner; + private _actionRunner: IActionRunner | undefined; constructor(context: any, action: IAction, protected options?: IBaseActionViewItemOptions) { super(); @@ -81,12 +82,16 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { } } - set actionRunner(actionRunner: IActionRunner) { - this._actionRunner = actionRunner; + get actionRunner(): IActionRunner { + if (!this._actionRunner) { + this._actionRunner = this._register(new ActionRunner()); + } + + return this._actionRunner; } - get actionRunner(): IActionRunner { - return this._actionRunner; + set actionRunner(actionRunner: IActionRunner) { + this._actionRunner = actionRunner; } getAction(): IAction { @@ -102,7 +107,7 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { } render(container: HTMLElement): void { - this.element = container; + const element = this.element = container; this._register(Gesture.addTarget(container)); const enableDragging = this.options && this.options.draggable; @@ -110,19 +115,19 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { container.draggable = true; } - this._register(DOM.addDisposableListener(this.element, EventType.Tap, e => this.onClick(e))); + this._register(DOM.addDisposableListener(element, EventType.Tap, e => this.onClick(e))); - this._register(DOM.addDisposableListener(this.element, DOM.EventType.MOUSE_DOWN, e => { + this._register(DOM.addDisposableListener(element, DOM.EventType.MOUSE_DOWN, e => { if (!enableDragging) { DOM.EventHelper.stop(e, true); // do not run when dragging is on because that would disable it } - if (this._action.enabled && e.button === 0 && this.element) { - DOM.addClass(this.element, 'active'); + if (this._action.enabled && e.button === 0) { + DOM.addClass(element, 'active'); } })); - this._register(DOM.addDisposableListener(this.element, DOM.EventType.CLICK, e => { + this._register(DOM.addDisposableListener(element, DOM.EventType.CLICK, e => { DOM.EventHelper.stop(e, true); // See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Interact_with_the_clipboard // > Writing to the clipboard @@ -139,14 +144,14 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { } })); - this._register(DOM.addDisposableListener(this.element, DOM.EventType.DBLCLICK, e => { + this._register(DOM.addDisposableListener(element, DOM.EventType.DBLCLICK, e => { DOM.EventHelper.stop(e, true); })); [DOM.EventType.MOUSE_UP, DOM.EventType.MOUSE_OUT].forEach(event => { - this._register(DOM.addDisposableListener(this.element!, event, e => { + this._register(DOM.addDisposableListener(element, event, e => { DOM.EventHelper.stop(e); - DOM.removeClass(this.element!, 'active'); + DOM.removeClass(element, 'active'); })); }); } @@ -165,7 +170,7 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { } } - this._actionRunner.run(this._action, context); + this.actionRunner.run(this._action, context); } focus(): void { @@ -219,7 +224,6 @@ export class Separator extends Action { constructor(label?: string) { super(Separator.ID, label, label ? 'separator text' : 'separator'); this.checked = false; - this.radio = false; this.enabled = false; } } @@ -232,7 +236,7 @@ export interface IActionViewItemOptions extends IBaseActionViewItemOptions { export class ActionViewItem extends BaseActionViewItem { - protected label!: HTMLElement; + protected label: HTMLElement | undefined; protected options: IActionViewItemOptions; private cssClass?: string; @@ -252,13 +256,17 @@ export class ActionViewItem extends BaseActionViewItem { if (this.element) { this.label = DOM.append(this.element, DOM.$('a.action-label')); } - if (this._action.id === Separator.ID) { - this.label.setAttribute('role', 'presentation'); // A separator is a presentation item - } else { - if (this.options.isMenu) { - this.label.setAttribute('role', 'menuitem'); + + + if (this.label) { + if (this._action.id === Separator.ID) { + this.label.setAttribute('role', 'presentation'); // A separator is a presentation item } else { - this.label.setAttribute('role', 'button'); + if (this.options.isMenu) { + this.label.setAttribute('role', 'menuitem'); + } else { + this.label.setAttribute('role', 'button'); + } } } @@ -276,11 +284,13 @@ export class ActionViewItem extends BaseActionViewItem { focus(): void { super.focus(); - this.label.focus(); + if (this.label) { + this.label.focus(); + } } updateLabel(): void { - if (this.options.label) { + if (this.options.label && this.label) { this.label.textContent = this.getAction().label; } } @@ -299,52 +309,65 @@ export class ActionViewItem extends BaseActionViewItem { } } - if (title) { + if (title && this.label) { this.label.title = title; } } updateClass(): void { - if (this.cssClass) { + if (this.cssClass && this.label) { DOM.removeClasses(this.label, this.cssClass); } if (this.options.icon) { this.cssClass = this.getAction().class; - DOM.addClass(this.label, 'codicon'); - if (this.cssClass) { - DOM.addClasses(this.label, this.cssClass); + + if (this.label) { + DOM.addClass(this.label, 'codicon'); + if (this.cssClass) { + DOM.addClasses(this.label, this.cssClass); + } } this.updateEnabled(); } else { - DOM.removeClass(this.label, 'codicon'); + if (this.label) { + DOM.removeClass(this.label, 'codicon'); + } } } updateEnabled(): void { if (this.getAction().enabled) { - this.label.removeAttribute('aria-disabled'); + if (this.label) { + this.label.removeAttribute('aria-disabled'); + DOM.removeClass(this.label, 'disabled'); + this.label.tabIndex = 0; + } + if (this.element) { DOM.removeClass(this.element, 'disabled'); } - DOM.removeClass(this.label, 'disabled'); - this.label.tabIndex = 0; } else { - this.label.setAttribute('aria-disabled', 'true'); + if (this.label) { + this.label.setAttribute('aria-disabled', 'true'); + DOM.addClass(this.label, 'disabled'); + DOM.removeTabIndexAndUpdateFocus(this.label); + } + if (this.element) { DOM.addClass(this.element, 'disabled'); } - DOM.addClass(this.label, 'disabled'); - DOM.removeTabIndexAndUpdateFocus(this.label); } } updateChecked(): void { - if (this.getAction().checked) { - DOM.addClass(this.label, 'checked'); - } else { - DOM.removeClass(this.label, 'checked'); + if (this.label) { + if (this.getAction().checked) { + DOM.addClass(this.label, 'checked'); + } else { + DOM.removeClass(this.label, 'checked'); + } } } } @@ -745,9 +768,9 @@ export class ActionBar extends Disposable implements IActionRunner { this.updateFocus(true); } - protected updateFocus(fromRight?: boolean): void { + protected updateFocus(fromRight?: boolean, preventScroll?: boolean): void { if (typeof this.focusedItem === 'undefined') { - this.actionsList.focus(); + this.actionsList.focus({ preventScroll }); } for (let i = 0; i < this.viewItems.length; i++) { @@ -759,7 +782,7 @@ export class ActionBar extends Disposable implements IActionRunner { if (actionViewItem.isEnabled() && types.isFunction(actionViewItem.focus)) { actionViewItem.focus(fromRight); } else { - this.actionsList.focus(); + this.actionsList.focus({ preventScroll }); } } } else { diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index 63e5f26a55..8015afc2ca 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -79,7 +79,7 @@ export class Button extends Disposable { this._register(DOM.addDisposableListener(this._element, DOM.EventType.KEY_DOWN, e => { const event = new StandardKeyboardEvent(e); let eventHandled = false; - if (this.enabled && event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { + if (this.enabled && (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space))) { this._onDidClick.fire(e); eventHandled = true; } else if (event.equals(KeyCode.Escape)) { @@ -129,15 +129,15 @@ export class Button extends Disposable { // {{SQL CARBON EDIT}} -- removed 'private' access modifier @todo anthonydresser 4/12/19 things needs investigation whether we need this applyStyles(): void { if (this._element) { - const background = this.buttonBackground ? this.buttonBackground.toString() : null; + const background = this.buttonBackground ? this.buttonBackground.toString() : ''; const foreground = this.buttonForeground ? this.buttonForeground.toString() : null; - const border = this.buttonBorder ? this.buttonBorder.toString() : null; + const border = this.buttonBorder ? this.buttonBorder.toString() : ''; this._element.style.color = foreground; this._element.style.backgroundColor = background; - this._element.style.borderWidth = border ? '1px' : null; - this._element.style.borderStyle = border ? 'solid' : null; + this._element.style.borderWidth = border ? '1px' : ''; + this._element.style.borderStyle = border ? 'solid' : ''; this._element.style.borderColor = border; } } diff --git a/src/vs/base/browser/ui/checkbox/checkbox.ts b/src/vs/base/browser/ui/checkbox/checkbox.ts index b044557e91..e74d09c7e3 100644 --- a/src/vs/base/browser/ui/checkbox/checkbox.ts +++ b/src/vs/base/browser/ui/checkbox/checkbox.ts @@ -38,7 +38,7 @@ const defaultOpts = { export class CheckboxActionViewItem extends BaseActionViewItem { - private checkbox!: Checkbox; + private checkbox: Checkbox | undefined; private readonly disposables = new DisposableStore(); render(container: HTMLElement): void { @@ -51,7 +51,7 @@ export class CheckboxActionViewItem extends BaseActionViewItem { title: this._action.label }); this.disposables.add(this.checkbox); - this.disposables.add(this.checkbox.onChange(() => this._action.checked = this.checkbox!.checked, this)); + this.disposables.add(this.checkbox.onChange(() => this._action.checked = !!this.checkbox && this.checkbox.checked, this)); this.element.appendChild(this.checkbox.domNode); } @@ -219,7 +219,7 @@ export class SimpleCheckbox extends Widget { protected applyStyles(): void { this.domNode.style.color = this.styles.checkboxForeground ? this.styles.checkboxForeground.toString() : null; - this.domNode.style.backgroundColor = this.styles.checkboxBackground ? this.styles.checkboxBackground.toString() : null; - this.domNode.style.borderColor = this.styles.checkboxBorder ? this.styles.checkboxBorder.toString() : null; + this.domNode.style.backgroundColor = this.styles.checkboxBackground ? this.styles.checkboxBackground.toString() : ''; + this.domNode.style.borderColor = this.styles.checkboxBorder ? this.styles.checkboxBorder.toString() : ''; } } diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon-animations.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon-animations.css index b568eed82d..6ba05a2b0d 100644 --- a/src/vs/base/browser/ui/codiconLabel/codicon/codicon-animations.css +++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon-animations.css @@ -10,5 +10,5 @@ } .codicon-animation-spin { - animation: octicon-spin 1.5s linear infinite; + animation: codicon-spin 1.5s linear infinite; } diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css index 2972c42d70..1f4a433995 100644 --- a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css +++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css @@ -5,7 +5,7 @@ @font-face { font-family: "codicon"; - src: url("./codicon.ttf?3b584136fb1f0186a1ee578cdcb5240b") format("truetype"); + src: url("./codicon.ttf?10ac421d405314bb3250169d97fc2c62") format("truetype"); } .codicon[class*='codicon-'] { @@ -54,6 +54,7 @@ .codicon-star:before { content: "\ea6a" } .codicon-star-add:before { content: "\ea6a" } .codicon-star-delete:before { content: "\ea6a" } +.codicon-star-empty:before { content: "\ea6a" } .codicon-comment:before { content: "\ea6b" } .codicon-comment-add:before { content: "\ea6b" } .codicon-alert:before { content: "\ea6c" } @@ -69,7 +70,6 @@ .codicon-eye-watch:before { content: "\ea70" } .codicon-circle-filled:before { content: "\ea71" } .codicon-primitive-dot:before { content: "\ea71" } -.codicon-stop:before { content: "\ea72" } .codicon-primitive-square:before { content: "\ea72" } .codicon-edit:before { content: "\ea73" } .codicon-pencil:before { content: "\ea73" } @@ -103,253 +103,264 @@ .codicon-file-add:before { content: "\ea7f" } .codicon-new-folder:before { content: "\ea80" } .codicon-file-directory-create:before { content: "\ea80" } -.codicon-Vector:before { content: "\f101" } -.codicon-activate-breakpoints:before { content: "\f102" } -.codicon-archive:before { content: "\f103" } -.codicon-array:before { content: "\f104" } -.codicon-arrow-both:before { content: "\f105" } -.codicon-arrow-down:before { content: "\f106" } -.codicon-arrow-left:before { content: "\f107" } -.codicon-arrow-right:before { content: "\f108" } -.codicon-arrow-small-down:before { content: "\f109" } -.codicon-arrow-small-left:before { content: "\f10a" } -.codicon-arrow-small-right:before { content: "\f10b" } -.codicon-arrow-small-up:before { content: "\f10c" } -.codicon-arrow-up:before { content: "\f10d" } -.codicon-bell:before { content: "\f10e" } -.codicon-bold:before { content: "\f10f" } -.codicon-book:before { content: "\f110" } -.codicon-bookmark:before { content: "\f111" } -.codicon-boolean:before { content: "\f112" } -.codicon-breakpoint-conditional-unverified:before { content: "\f113" } -.codicon-breakpoint-conditional:before { content: "\f114" } -.codicon-breakpoint-data-unverified:before { content: "\f115" } -.codicon-breakpoint-data:before { content: "\f116" } -.codicon-breakpoint-log-unverified:before { content: "\f117" } -.codicon-breakpoint-log:before { content: "\f118" } -.codicon-briefcase:before { content: "\f119" } -.codicon-broadcast:before { content: "\f11a" } -.codicon-browser:before { content: "\f11b" } -.codicon-bug:before { content: "\f11c" } -.codicon-calendar:before { content: "\f11d" } -.codicon-case-sensitive:before { content: "\f11e" } -.codicon-check:before { content: "\f11f" } -.codicon-checklist:before { content: "\f120" } -.codicon-chevron-down:before { content: "\f121" } -.codicon-chevron-left:before { content: "\f122" } -.codicon-chevron-right:before { content: "\f123" } -.codicon-chevron-up:before { content: "\f124" } -.codicon-circle-outline:before { content: "\f125" } -.codicon-circle-slash:before { content: "\f126" } -.codicon-circuit-board:before { content: "\f127" } -.codicon-class:before { content: "\f128" } -.codicon-clear-all:before { content: "\f129" } -.codicon-clippy:before { content: "\f12a" } -.codicon-close-all:before { content: "\f12b" } -.codicon-cloud-download:before { content: "\f12c" } -.codicon-cloud-upload:before { content: "\f12d" } -.codicon-code:before { content: "\f12e" } -.codicon-collapse-all:before { content: "\f12f" } -.codicon-color-mode:before { content: "\f130" } -.codicon-color:before { content: "\f131" } -.codicon-comment-discussion:before { content: "\f132" } -.codicon-compare-changes:before { content: "\f133" } -.codicon-console:before { content: "\f134" } -.codicon-constant:before { content: "\f135" } -.codicon-continue:before { content: "\f136" } -.codicon-credit-card:before { content: "\f137" } -.codicon-current-and-breakpoint:before { content: "\f138" } -.codicon-current:before { content: "\f139" } -.codicon-dash:before { content: "\f13a" } -.codicon-dashboard:before { content: "\f13b" } -.codicon-database:before { content: "\f13c" } -.codicon-debug:before { content: "\f13d" } -.codicon-device-camera-video:before { content: "\f13e" } -.codicon-device-camera:before { content: "\f13f" } -.codicon-device-mobile:before { content: "\f140" } -.codicon-diff-added:before { content: "\f141" } -.codicon-diff-ignored:before { content: "\f142" } -.codicon-diff-modified:before { content: "\f143" } -.codicon-diff-removed:before { content: "\f144" } -.codicon-diff-renamed:before { content: "\f145" } -.codicon-diff:before { content: "\f146" } -.codicon-discard:before { content: "\f147" } -.codicon-disconnect-:before { content: "\f148" } -.codicon-editor-layout:before { content: "\f149" } -.codicon-ellipsis:before { content: "\f14a" } -.codicon-empty-window:before { content: "\f14b" } -.codicon-enumerator-member:before { content: "\f14c" } -.codicon-enumerator:before { content: "\f14d" } -.codicon-error:before { content: "\f14e" } -.codicon-event:before { content: "\f14f" } -.codicon-exclude:before { content: "\f150" } -.codicon-extensions:before { content: "\f151" } -.codicon-eye-closed:before { content: "\f152" } -.codicon-field:before { content: "\f153" } -.codicon-file-binary:before { content: "\f154" } -.codicon-file-code:before { content: "\f155" } -.codicon-file-media:before { content: "\f156" } -.codicon-file-pdf:before { content: "\f157" } -.codicon-file-submodule:before { content: "\f158" } -.codicon-file-symlink-directory:before { content: "\f159" } -.codicon-file-symlink-file:before { content: "\f15a" } -.codicon-file-zip:before { content: "\f15b" } -.codicon-files:before { content: "\f15c" } -.codicon-filter:before { content: "\f15d" } -.codicon-flame:before { content: "\f15e" } -.codicon-fold-down:before { content: "\f15f" } -.codicon-fold-up:before { content: "\f160" } -.codicon-fold:before { content: "\f161" } -.codicon-folder-active:before { content: "\f162" } -.codicon-folder-opened:before { content: "\f163" } -.codicon-folder:before { content: "\f164" } -.codicon-gear:before { content: "\f165" } -.codicon-gift:before { content: "\f166" } -.codicon-gist-secret:before { content: "\f167" } -.codicon-gist:before { content: "\f168" } -.codicon-git-commit:before { content: "\f169" } -.codicon-git-compare:before { content: "\f16a" } -.codicon-git-merge:before { content: "\f16b" } -.codicon-github-action:before { content: "\f16c" } -.codicon-github-alt:before { content: "\f16d" } -.codicon-github:before { content: "\f16e" } -.codicon-globe:before { content: "\f16f" } -.codicon-go-to-file:before { content: "\f170" } -.codicon-grabber:before { content: "\f171" } -.codicon-graph:before { content: "\f172" } -.codicon-gripper:before { content: "\f173" } -.codicon-heart:before { content: "\f174" } -.codicon-history:before { content: "\f175" } -.codicon-home:before { content: "\f176" } -.codicon-horizontal-rule:before { content: "\f177" } -.codicon-hubot:before { content: "\f178" } -.codicon-inbox:before { content: "\f179" } -.codicon-interface:before { content: "\f17a" } -.codicon-issue-closed:before { content: "\f17b" } -.codicon-issue-reopened:before { content: "\f17c" } -.codicon-issues:before { content: "\f17d" } -.codicon-italic:before { content: "\f17e" } -.codicon-jersey:before { content: "\f17f" } -.codicon-json:before { content: "\f180" } -.codicon-kebab-vertical:before { content: "\f181" } -.codicon-key:before { content: "\f182" } -.codicon-keyword:before { content: "\f183" } -.codicon-law:before { content: "\f184" } -.codicon-lightbulb-autofix:before { content: "\f185" } -.codicon-link-external:before { content: "\f186" } -.codicon-link:before { content: "\f187" } -.codicon-list-ordered:before { content: "\f188" } -.codicon-list-unordered:before { content: "\f189" } -.codicon-live-share:before { content: "\f18a" } -.codicon-loading:before { content: "\f18b" } -.codicon-location:before { content: "\f18c" } -.codicon-mail-read:before { content: "\f18d" } -.codicon-mail:before { content: "\f18e" } -.codicon-markdown:before { content: "\f18f" } -.codicon-megaphone:before { content: "\f190" } -.codicon-mention:before { content: "\f191" } -.codicon-method:before { content: "\f192" } -.codicon-milestone:before { content: "\f193" } -.codicon-misc:before { content: "\f194" } -.codicon-mortar-board:before { content: "\f195" } -.codicon-move:before { content: "\f196" } -.codicon-multiple-windows:before { content: "\f197" } -.codicon-mute:before { content: "\f198" } -.codicon-namespace:before { content: "\f199" } -.codicon-no-newline:before { content: "\f19a" } -.codicon-note:before { content: "\f19b" } -.codicon-numeric:before { content: "\f19c" } -.codicon-octoface:before { content: "\f19d" } -.codicon-open-preview:before { content: "\f19e" } -.codicon-operator:before { content: "\f19f" } -.codicon-package:before { content: "\f1a0" } -.codicon-paintcan:before { content: "\f1a1" } -.codicon-parameter:before { content: "\f1a2" } -.codicon-pause:before { content: "\f1a3" } -.codicon-pin:before { content: "\f1a4" } -.codicon-play:before { content: "\f1a5" } -.codicon-plug:before { content: "\f1a6" } -.codicon-preserve-case:before { content: "\f1a7" } -.codicon-preview:before { content: "\f1a8" } -.codicon-project:before { content: "\f1a9" } -.codicon-property:before { content: "\f1aa" } -.codicon-pulse:before { content: "\f1ab" } -.codicon-question:before { content: "\f1ac" } -.codicon-quote:before { content: "\f1ad" } -.codicon-radio-tower:before { content: "\f1ae" } -.codicon-reactions:before { content: "\f1af" } -.codicon-references:before { content: "\f1b0" } -.codicon-refresh:before { content: "\f1b1" } -.codicon-regex:before { content: "\f1b2" } -.codicon-remote:before { content: "\f1b3" } -.codicon-remove:before { content: "\f1b4" } -.codicon-replace-all:before { content: "\f1b5" } -.codicon-replace:before { content: "\f1b6" } -.codicon-repo-clone:before { content: "\f1b7" } -.codicon-repo-force-push:before { content: "\f1b8" } -.codicon-repo-pull:before { content: "\f1b9" } -.codicon-repo-push:before { content: "\f1ba" } -.codicon-report:before { content: "\f1bb" } -.codicon-request-changes:before { content: "\f1bc" } -.codicon-restart:before { content: "\f1bd" } -.codicon-rocket:before { content: "\f1be" } -.codicon-root-folder-opened:before { content: "\f1bf" } -.codicon-root-folder:before { content: "\f1c0" } -.codicon-rss:before { content: "\f1c1" } -.codicon-ruby:before { content: "\f1c2" } -.codicon-ruler:before { content: "\f1c3" } -.codicon-save-all:before { content: "\f1c4" } -.codicon-save-as:before { content: "\f1c5" } -.codicon-save:before { content: "\f1c6" } -.codicon-screen-full:before { content: "\f1c7" } -.codicon-screen-normal:before { content: "\f1c8" } -.codicon-search-stop:before { content: "\f1c9" } -.codicon-selection:before { content: "\f1ca" } -.codicon-server:before { content: "\f1cb" } -.codicon-settings:before { content: "\f1cc" } -.codicon-shield:before { content: "\f1cd" } -.codicon-smiley:before { content: "\f1ce" } -.codicon-snippet:before { content: "\f1cf" } -.codicon-sort-precedence:before { content: "\f1d0" } -.codicon-split-horizontal:before { content: "\f1d1" } -.codicon-split-vertical:before { content: "\f1d2" } -.codicon-squirrel:before { content: "\f1d3" } -.codicon-star-empty:before { content: "\f1d4" } -.codicon-star-full:before { content: "\f1d5" } -.codicon-star-half:before { content: "\f1d6" } -.codicon-start:before { content: "\f1d7" } -.codicon-step-into:before { content: "\f1d8" } -.codicon-step-out:before { content: "\f1d9" } -.codicon-step-over:before { content: "\f1da" } -.codicon-string:before { content: "\f1db" } -.codicon-structure:before { content: "\f1dc" } -.codicon-tasklist:before { content: "\f1dd" } -.codicon-telescope:before { content: "\f1de" } -.codicon-text-size:before { content: "\f1df" } -.codicon-three-bars:before { content: "\f1e0" } -.codicon-thumbsdown:before { content: "\f1e1" } -.codicon-thumbsup:before { content: "\f1e2" } -.codicon-tools:before { content: "\f1e3" } -.codicon-trash:before { content: "\f1e4" } -.codicon-triangle-down:before { content: "\f1e5" } -.codicon-triangle-left:before { content: "\f1e6" } -.codicon-triangle-right:before { content: "\f1e7" } -.codicon-triangle-up:before { content: "\f1e8" } -.codicon-twitter:before { content: "\f1e9" } -.codicon-unfold:before { content: "\f1ea" } -.codicon-unlock:before { content: "\f1eb" } -.codicon-unmute:before { content: "\f1ec" } -.codicon-unverified:before { content: "\f1ed" } -.codicon-variable:before { content: "\f1ee" } -.codicon-verified:before { content: "\f1ef" } -.codicon-versions:before { content: "\f1f0" } -.codicon-vm-active:before { content: "\f1f1" } -.codicon-vm-outline:before { content: "\f1f2" } -.codicon-vm-running:before { content: "\f1f3" } -.codicon-watch:before { content: "\f1f4" } -.codicon-whitespace:before { content: "\f1f5" } -.codicon-whole-word:before { content: "\f1f6" } -.codicon-window:before { content: "\f1f7" } -.codicon-word-wrap:before { content: "\f1f8" } -.codicon-zoom-in:before { content: "\f1f9" } -.codicon-zoom-out:before { content: "\f1fa" } +.codicon-trash:before { content: "\ea81" } +.codicon-trashcan:before { content: "\ea81" } +.codicon-history:before { content: "\ea82" } +.codicon-clock:before { content: "\ea82" } +.codicon-folder:before { content: "\ea83" } +.codicon-file-directory:before { content: "\ea83" } +.codicon-logo-github:before { content: "\ea84" } +.codicon-mark-github:before { content: "\ea84" } +.codicon-github:before { content: "\ea84" } +.codicon-terminal:before { content: "\ea85" } +.codicon-console:before { content: "\ea85" } +.codicon-zap:before { content: "\ea86" } +.codicon-event:before { content: "\ea86" } +.codicon-error:before { content: "\ea87" } +.codicon-stop:before { content: "\ea87" } +.codicon-activate-breakpoints:before { content: "\f101" } +.codicon-archive:before { content: "\f102" } +.codicon-array:before { content: "\f103" } +.codicon-arrow-both:before { content: "\f104" } +.codicon-arrow-down:before { content: "\f105" } +.codicon-arrow-left:before { content: "\f106" } +.codicon-arrow-right:before { content: "\f107" } +.codicon-arrow-small-down:before { content: "\f108" } +.codicon-arrow-small-left:before { content: "\f109" } +.codicon-arrow-small-right:before { content: "\f10a" } +.codicon-arrow-small-up:before { content: "\f10b" } +.codicon-arrow-up:before { content: "\f10c" } +.codicon-bell:before { content: "\f10d" } +.codicon-bold:before { content: "\f10e" } +.codicon-book:before { content: "\f10f" } +.codicon-bookmark:before { content: "\f110" } +.codicon-boolean:before { content: "\f111" } +.codicon-breakpoint-conditional-unverified:before { content: "\f112" } +.codicon-breakpoint-conditional:before { content: "\f113" } +.codicon-breakpoint-data-unverified:before { content: "\f114" } +.codicon-breakpoint-data:before { content: "\f115" } +.codicon-breakpoint-log-unverified:before { content: "\f116" } +.codicon-breakpoint-log:before { content: "\f117" } +.codicon-briefcase:before { content: "\f118" } +.codicon-broadcast:before { content: "\f119" } +.codicon-browser:before { content: "\f11a" } +.codicon-bug:before { content: "\f11b" } +.codicon-calendar:before { content: "\f11c" } +.codicon-case-sensitive:before { content: "\f11d" } +.codicon-check:before { content: "\f11e" } +.codicon-checklist:before { content: "\f11f" } +.codicon-chevron-down:before { content: "\f120" } +.codicon-chevron-left:before { content: "\f121" } +.codicon-chevron-right:before { content: "\f122" } +.codicon-chevron-up:before { content: "\f123" } +.codicon-chrome-close:before { content: "\f124" } +.codicon-chrome-maximize:before { content: "\f125" } +.codicon-chrome-minimize:before { content: "\f126" } +.codicon-chrome-restore:before { content: "\f127" } +.codicon-circle-outline:before { content: "\f128" } +.codicon-circle-slash:before { content: "\f129" } +.codicon-circuit-board:before { content: "\f12a" } +.codicon-class:before { content: "\f12b" } +.codicon-clear-all:before { content: "\f12c" } +.codicon-clippy:before { content: "\f12d" } +.codicon-close-all:before { content: "\f12e" } +.codicon-cloud-download:before { content: "\f12f" } +.codicon-cloud-upload:before { content: "\f130" } +.codicon-code:before { content: "\f131" } +.codicon-collapse-all:before { content: "\f132" } +.codicon-color-mode:before { content: "\f133" } +.codicon-color:before { content: "\f134" } +.codicon-comment-discussion:before { content: "\f135" } +.codicon-compare-changes:before { content: "\f136" } +.codicon-constant:before { content: "\f137" } +.codicon-continue:before { content: "\f138" } +.codicon-credit-card:before { content: "\f139" } +.codicon-current-and-breakpoint:before { content: "\f13a" } +.codicon-current:before { content: "\f13b" } +.codicon-dash:before { content: "\f13c" } +.codicon-dashboard:before { content: "\f13d" } +.codicon-database:before { content: "\f13e" } +.codicon-debug-disconnect:before { content: "\f13f" } +.codicon-debug-pause:before { content: "\f140" } +.codicon-debug-restart:before { content: "\f141" } +.codicon-debug-start:before { content: "\f142" } +.codicon-debug-step-into:before { content: "\f143" } +.codicon-debug-step-out:before { content: "\f144" } +.codicon-debug-step-over:before { content: "\f145" } +.codicon-debug-stop:before { content: "\f146" } +.codicon-debug:before { content: "\f147" } +.codicon-device-camera-video:before { content: "\f148" } +.codicon-device-camera:before { content: "\f149" } +.codicon-device-mobile:before { content: "\f14a" } +.codicon-diff-added:before { content: "\f14b" } +.codicon-diff-ignored:before { content: "\f14c" } +.codicon-diff-modified:before { content: "\f14d" } +.codicon-diff-removed:before { content: "\f14e" } +.codicon-diff-renamed:before { content: "\f14f" } +.codicon-diff:before { content: "\f150" } +.codicon-discard:before { content: "\f151" } +.codicon-editor-layout:before { content: "\f152" } +.codicon-ellipsis:before { content: "\f153" } +.codicon-empty-window:before { content: "\f154" } +.codicon-enumerator-member:before { content: "\f155" } +.codicon-enumerator:before { content: "\f156" } +.codicon-exclude:before { content: "\f157" } +.codicon-extensions:before { content: "\f158" } +.codicon-eye-closed:before { content: "\f159" } +.codicon-field:before { content: "\f15a" } +.codicon-file-binary:before { content: "\f15b" } +.codicon-file-code:before { content: "\f15c" } +.codicon-file-media:before { content: "\f15d" } +.codicon-file-pdf:before { content: "\f15e" } +.codicon-file-submodule:before { content: "\f15f" } +.codicon-file-symlink-directory:before { content: "\f160" } +.codicon-file-symlink-file:before { content: "\f161" } +.codicon-file-zip:before { content: "\f162" } +.codicon-files:before { content: "\f163" } +.codicon-filter:before { content: "\f164" } +.codicon-flame:before { content: "\f165" } +.codicon-fold-down:before { content: "\f166" } +.codicon-fold-up:before { content: "\f167" } +.codicon-fold:before { content: "\f168" } +.codicon-folder-active:before { content: "\f169" } +.codicon-folder-opened:before { content: "\f16a" } +.codicon-gear:before { content: "\f16b" } +.codicon-gift:before { content: "\f16c" } +.codicon-gist-secret:before { content: "\f16d" } +.codicon-gist:before { content: "\f16e" } +.codicon-git-commit:before { content: "\f16f" } +.codicon-git-compare:before { content: "\f170" } +.codicon-git-merge:before { content: "\f171" } +.codicon-github-action:before { content: "\f172" } +.codicon-github-alt:before { content: "\f173" } +.codicon-globe:before { content: "\f174" } +.codicon-go-to-file:before { content: "\f175" } +.codicon-grabber:before { content: "\f176" } +.codicon-graph:before { content: "\f177" } +.codicon-gripper:before { content: "\f178" } +.codicon-heart:before { content: "\f179" } +.codicon-home:before { content: "\f17a" } +.codicon-horizontal-rule:before { content: "\f17b" } +.codicon-hubot:before { content: "\f17c" } +.codicon-inbox:before { content: "\f17d" } +.codicon-interface:before { content: "\f17e" } +.codicon-issue-closed:before { content: "\f17f" } +.codicon-issue-reopened:before { content: "\f180" } +.codicon-issues:before { content: "\f181" } +.codicon-italic:before { content: "\f182" } +.codicon-jersey:before { content: "\f183" } +.codicon-json:before { content: "\f184" } +.codicon-kebab-vertical:before { content: "\f185" } +.codicon-key:before { content: "\f186" } +.codicon-keyword:before { content: "\f187" } +.codicon-law:before { content: "\f188" } +.codicon-lightbulb-autofix:before { content: "\f189" } +.codicon-link-external:before { content: "\f18a" } +.codicon-link:before { content: "\f18b" } +.codicon-list-ordered:before { content: "\f18c" } +.codicon-list-unordered:before { content: "\f18d" } +.codicon-live-share:before { content: "\f18e" } +.codicon-loading:before { content: "\f18f" } +.codicon-location:before { content: "\f190" } +.codicon-mail-read:before { content: "\f191" } +.codicon-mail:before { content: "\f192" } +.codicon-markdown:before { content: "\f193" } +.codicon-megaphone:before { content: "\f194" } +.codicon-mention:before { content: "\f195" } +.codicon-method:before { content: "\f196" } +.codicon-milestone:before { content: "\f197" } +.codicon-misc:before { content: "\f198" } +.codicon-mortar-board:before { content: "\f199" } +.codicon-move:before { content: "\f19a" } +.codicon-multiple-windows:before { content: "\f19b" } +.codicon-mute:before { content: "\f19c" } +.codicon-namespace:before { content: "\f19d" } +.codicon-no-newline:before { content: "\f19e" } +.codicon-note:before { content: "\f19f" } +.codicon-numeric:before { content: "\f1a0" } +.codicon-octoface:before { content: "\f1a1" } +.codicon-open-preview:before { content: "\f1a2" } +.codicon-operator:before { content: "\f1a3" } +.codicon-package:before { content: "\f1a4" } +.codicon-paintcan:before { content: "\f1a5" } +.codicon-parameter:before { content: "\f1a6" } +.codicon-pin:before { content: "\f1a7" } +.codicon-play:before { content: "\f1a8" } +.codicon-plug:before { content: "\f1a9" } +.codicon-preserve-case:before { content: "\f1aa" } +.codicon-preview:before { content: "\f1ab" } +.codicon-project:before { content: "\f1ac" } +.codicon-property:before { content: "\f1ad" } +.codicon-pulse:before { content: "\f1ae" } +.codicon-question:before { content: "\f1af" } +.codicon-quote:before { content: "\f1b0" } +.codicon-radio-tower:before { content: "\f1b1" } +.codicon-reactions:before { content: "\f1b2" } +.codicon-references:before { content: "\f1b3" } +.codicon-refresh:before { content: "\f1b4" } +.codicon-regex:before { content: "\f1b5" } +.codicon-remote:before { content: "\f1b6" } +.codicon-remove:before { content: "\f1b7" } +.codicon-replace-all:before { content: "\f1b8" } +.codicon-replace:before { content: "\f1b9" } +.codicon-repo-clone:before { content: "\f1ba" } +.codicon-repo-force-push:before { content: "\f1bb" } +.codicon-repo-pull:before { content: "\f1bc" } +.codicon-repo-push:before { content: "\f1bd" } +.codicon-report:before { content: "\f1be" } +.codicon-request-changes:before { content: "\f1bf" } +.codicon-rocket:before { content: "\f1c0" } +.codicon-root-folder-opened:before { content: "\f1c1" } +.codicon-root-folder:before { content: "\f1c2" } +.codicon-rss:before { content: "\f1c3" } +.codicon-ruby:before { content: "\f1c4" } +.codicon-ruler:before { content: "\f1c5" } +.codicon-save-all:before { content: "\f1c6" } +.codicon-save-as:before { content: "\f1c7" } +.codicon-save:before { content: "\f1c8" } +.codicon-screen-full:before { content: "\f1c9" } +.codicon-screen-normal:before { content: "\f1ca" } +.codicon-search-stop:before { content: "\f1cb" } +.codicon-selection:before { content: "\f1cc" } +.codicon-server:before { content: "\f1cd" } +.codicon-settings:before { content: "\f1ce" } +.codicon-shield:before { content: "\f1cf" } +.codicon-smiley:before { content: "\f1d0" } +.codicon-snippet:before { content: "\f1d1" } +.codicon-sort-precedence:before { content: "\f1d2" } +.codicon-split-horizontal:before { content: "\f1d3" } +.codicon-split-vertical:before { content: "\f1d4" } +.codicon-squirrel:before { content: "\f1d5" } +.codicon-star-full:before { content: "\f1d6" } +.codicon-star-half:before { content: "\f1d7" } +.codicon-string:before { content: "\f1d8" } +.codicon-structure:before { content: "\f1d9" } +.codicon-tasklist:before { content: "\f1da" } +.codicon-telescope:before { content: "\f1db" } +.codicon-text-size:before { content: "\f1dc" } +.codicon-three-bars:before { content: "\f1dd" } +.codicon-thumbsdown:before { content: "\f1de" } +.codicon-thumbsup:before { content: "\f1df" } +.codicon-tools:before { content: "\f1e0" } +.codicon-triangle-down:before { content: "\f1e1" } +.codicon-triangle-left:before { content: "\f1e2" } +.codicon-triangle-right:before { content: "\f1e3" } +.codicon-triangle-up:before { content: "\f1e4" } +.codicon-twitter:before { content: "\f1e5" } +.codicon-unfold:before { content: "\f1e6" } +.codicon-unlock:before { content: "\f1e7" } +.codicon-unmute:before { content: "\f1e8" } +.codicon-unverified:before { content: "\f1e9" } +.codicon-variable:before { content: "\f1ea" } +.codicon-verified:before { content: "\f1eb" } +.codicon-versions:before { content: "\f1ec" } +.codicon-vm-active:before { content: "\f1ed" } +.codicon-vm-outline:before { content: "\f1ee" } +.codicon-vm-running:before { content: "\f1ef" } +.codicon-watch:before { content: "\f1f0" } +.codicon-whitespace:before { content: "\f1f1" } +.codicon-whole-word:before { content: "\f1f2" } +.codicon-window:before { content: "\f1f3" } +.codicon-word-wrap:before { content: "\f1f4" } +.codicon-zoom-in:before { content: "\f1f5" } +.codicon-zoom-out:before { content: "\f1f6" } diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf index fff7ab51978252a9e4038024ab52f11a530a95e7..93e1eec8e27bd8349c64cae07f87c855e83048d2 100644 GIT binary patch delta 5353 zcmY+I3t&{`mB;_zog|a@lSwiOVUi4a0C@mIG9iSS@O~PCr7lH45(or&B#u}UdxCi_3Rva6H)=FEM5=bU?9 zGq-o!w(hoVcL!zwYAb;I<}PVy^^G2M8Hkz=q$Dm_yn6nuX)kO8ocn?OnaxcN^U{A_ zR?Ks+k;-Q7u)89r^7Bd3w|PnXs_!>+hk3tGfQa_REpr=kR*qT^WHbXt&60*ytwt3- z;PFhJpS!eSNmJglStB+8ukZ#&Y->wf`~K+jT=4guK*DQ3IqCN2K=E7>x*q?_2a;;O zLY(bW?!gCVznXtnc6WD=wEfjq!R=@mvX?JjqjG|M)Dd+n_>OXhv{JTUVq|NuIx-xb z9OVf_A%Z6v=A-&ncQ;_p(Mn{`rBRqhX-Yh-;b{!U|DXk#uofDw&F27j_vpfor+5lK z!*1-s&#@Q3z&`vEzrud}8VB$jJcDQPFZeAE;dvb96(Rf%FW?wn#7hXAz{`xJSMhtC z!XNM&PU8)n!JFvBTR4kzIFAeXBi_NEa1odAF8&qo<1((`D*l2G@o$XEkMQsK7@y!j za2@}Nzrw`d@EQIKH=yy~_!3{?@3@VB;10SFMz?}e%1|~HsiKrUc;S>YC0%8xOyyKr zDqHnt1Vx2DiZ)b$Pl>ODRr1cgeI#5gB;L#h3x*5KMhl5^bFKwL27qAM(zT`)T=aFlX`1v3ujk1UvbFdw#H z7Q)`%ttJkqc9(}V75ZDga}Z4`kK#KfGxNs?qQV_N{n|YXAyT6S}=K` zWt*U1aBRXbXF0b@Ett&EWfm9|+{L3_ZVj5K+myYaeT9lIeKTg?U z!SW2f!GeXF$3;WJnhh-)5>{_$(U7nzLyLlh6&+d>B&_Ywq99?Fhu&ntdJiqi5>|d_ zQI@a=K#Q`3-2hsYB>^@D=xr8k5zwMJVY7f1%?b8Vispn31X?sFY$ed5IAK$P7R3qM z3$!Rs*l3_dal)1ZEs7I1ALyUT^QZRgL(rl*VMl@%%?W!G^ezi_DQK~RuwOxo6@;A& zT8tn)-V!4SyBV|?LD<)z_gS#RL5mRt$M}5o(-v%d&|(o`1B4cf2u@InMT9*OS}Y7HpNAF?5O#cM@c?1(hknI^QvkGhgK!>z7H<$v2GFN0I3qxdH~t`r`xkx?cMuK^ z(63u?gn$;$5L}}a&k&9k(Bc`w0Rvh*LpW+ci)RRj4ruWV;rIb9o*^7Wpx?IONCJJ1 z>ub7-(+RY=iEvJV7B>-2EYNh5z}W>_yhJ#~K#P|M=NV}665(V6{bvi#IMCuT!f6Lu zJVrS8K)+{!z~A{n+($SJL5uqc$0FztEI1%Riz^97C1`Oa;m`yvt|aKD6i*VxO)Z`z ziW^!yNt8i(%|h8I>B)e+sYq^!D~XDt6ju^urxaHb6-_CwBr1kdTuD?crMQx)ILc2g zdb+fDm8b+t@hVY?l;TyQk|h7dtwbeLer};sD8H~!sg&YwqS7eE-9)8Rio1!*puA}z zUuU$qo+u}!xSpsiN^w0=*_5{gK7W}kdtPHXkw3xl_dVQDv8u1CR`qJWTC1K^7mPZi z!?HXx0elGCcvn$r%X zUFwzIYj&>-y>2-kb6iTVO5d3NQbuOReHn){2WEC;UUK$!ZgQS>-p=Zk^<374tee@r zvKMDRll@`u={Zq3GXgmW`c(G0=$hl&pF1P3cixHoi2Tg_#{4b$H{4b32i=GJruUuP zcTeA|1tSag7JOEiTezmEx@c9ASzKKFWby5OHT|0VebWDl{$~eN4Y*w5D_K)=pyXW1 zt12+!*-N0*umJT{IxY5(<2^<`fJ7ms~ zOBKl#eJdI(4pw|t8C_XbdARbDx8A#>s<7&*p~lcPLpz7ws7|fUt-djA{;pSY7p1Nl0nS0|y z)3T1J*tcwr#*6$SiL_u+Z%06>aHpy&4R;w^yV@n;n2X)-+7=mYa!!nLD6h(^$W!Wt z@0@3}h2Ja*ca|udkMpx!^A4|BD%V7+3T0P$jyz>}R8_dMRN1=PjjiFXfof=pswxS0 z`NJ#_yoO(wgTLy1sQmv_icxNUnM(1bC|l#*`}_H2EAXBBggSB};sV`&vrRB|^4l<# z%!pCm7*!O*bzcd8>{U4`#~c2*=S?tA*L7V`Uw551JY44mzb`z=Z+JrMi+3jmH`II*+}2SN%-gv& z8nCmGAGxYmU?tCioz`20df*V8VStt9u(%U!AJOV^cfr{Bjv z{wl+DC);+04o>trqHUfEcZS1V5xn`pfXIGzdz zg+Bb^F_{XcXY^6_M4LOmC`q{;cKI)=@Tx+u&q(trucxxi;9+}IhFv*46<+!Gt8~xc z%A(4OA%*VzMAgTrG~Sq2zRE2pnk=JGi!2Z<-Y9VV^377Vef&Q;%F7;s$cA# z*SEGjTdCxz@ac7~*{(XTyRcv3xQw_0XIgHeJ-fhd+(;Sj*)n$Ey}rEoVR5r~o80Z` z%GGRJa|(mB*nz@T#Y> zzdoPMyxwiR6@I<0&bZa3%F2xAyBdA|p69vac&VMghaHY~g+4mvFziNeBgg22Q24b! zs+#z@&C6SsG?mU>+|t&R^vwo;uUoie;hO*7UbwVpJMo+2%bVKTTb4J4&Yvz+f#i8j zjVl(E&Rf_vw`J+lrn&8L){fSO6>Uuk)@Qk(Vfo!7J)e`TBW>+Xt)&Z>wznjHYrADd z`?n9bJk+#2_U_@9*2tcZT()gddrNCjop~e_Jaf<%8gsU{3N3s)SA~w88z=L}{{rjE BCCmT- delta 5472 zcmZvg4S1B*mB;`0oyfJJ0jW zeP`bH-gEDnbN=T{wm)k5{87t;k>p}PZ3l3}^0mwQ@m{3{Y(0SI+%0SFTDkxF)60R< z6~K>MR`o4g;h%rBozK2c;;Z<9J)AR#*M~{fsXa(A z>>eIYUr;tnYN558WFIn$QwwaZYaQmC%<9Y*eR+5o$Slp=rj=ZcycF*TF^$(2Jcv_x z0|PjUFYqif*o}lLq&Ggo@30V2ynt(QFCIoSKF3tJl@}|p2Pg1TJdSo5;r!F*hg8}L_HfE#fW7U5#5R@{a)SewK;tVcf| zyB!;_5qIEDY{DS!!e)FGU&GzF2T5$f*Rd7b@YlExe}iw}etZ*ui|zP3Jb)ee79K(h zJMnGo!gugpJc38@7`}(^;|KVA{189FkFl3CeuDjY0tfIU4&o_1jc1TX@;N+@f50L9 z3@$7@M-WCL}2`y3K^i22Je{ zn0C-{6DA-uoh~pHp=AzXQbNld!t{ieIfRJ{EprG{7Fy;ICNH$iAxvXv&Pg3{-L2-N zI^3Jojj89|6Ov2#`2rIx<#?kB(-(T72@@ImCKIMK^db``H}uUWOmk=|y1;~omURhJ zA6nKW6ai>imrxF%xo%SaP#K_QVM2|7<`*V_k*386p=>~l5kdih79)gG0xd=eMFm=n z5XuX*7$FoIXt6;kInZK*P<)cmVuVnKpv4HGB0-B0LT!Q;BNEPcag>D#^$J=RCR8qH zS(s46pc%>n)eM@EEKt{=#Rj2SXtA-07udoJF+wPN&|-v80HMVQp%g-k5kgUf79)i6 z2rWhkg%Vnf5K1Pr7?Jhw=eWg$q6jTE2nCh*#R#FcLf>mbm4z0AgnA1t1__lHS_~3u zFtivXRAXo{NT|!uVvtawp~WDfRzr(HLe+*Adn8E#hZci`QVuN!2}K=R3=+ya^n)f8 zdT23DDEZK0oKXCs#W-OZfEMF~1p!)&6P5;OF-}+{puZ#EKR3X70sXKED+jb}gp9aL zHbPiUpk*V3bp={BLRev-Wg~>O23j^kSaqOfBZTz_dbbHH5wvWI;Awt8dXEXq6SVA* zuuwtE4hf#)C>tcKUC^>Y!YT&+xC!eS^ga_-HfY%_VU2^9%@S#rv}~5J?m^3Dll;RH z2rauMEQZjsTY@7Te`>-42`xJ(ES1nto3Lm?%f<=IC$wywu#iGOYr>KWEr%d1uF!G_ z!ZOSJms1c{TxdB3VeN&MQxH~R=od^_kD=v6gq0auPDEIvq2)w`)f!q3L|C_>?fe*)P$V{w49o-$ACU%!fpdvJV4lY zK#KjLX$>qXl-+iBZ*d)%JvvG>{s?GM?!E5Qo_5bd z&o<9~&sonEZ>4vk_kltb))p=(+*Nq2$W}C`=%u2Miti~t=BxD$`1bi7{^|bRfzg5f zz_Ai%vShGicgdNO3#Ip!?k|0_^g^&ZxGnfXSzFl$Uh
KkhOHS?pP=%VO}+J@Tx+Q-I} zjCplTwr)+mtA0cM!TPfeXsByg+3(D=5@`7n$Ncs zx9n-TeATY8KN|af>-^T8t^3n0CQ z-ZS~^l#(girW~H~cJk`(t4~gyJN3b-r>0Gxwr$#}YkIG_@0xwnPj$Jv>be$mt?Am; zb+kLDJJ|h5_t_auGq%roex_|^`^@<>56ueBI^9#;Q`6JlGqYz=&r7pyvnS5pIVU)0 z#hmBod~)sBYqwr|^4d@5mdsr|ch_~!>zd}-=FOY;FeAsppRIE(_wr|J6_U;TL5J#S zOvRckF3Tuocd253K!w!UctY82Axnp$YC4QSNbz2gOI6lH8E|d9>2oQcEo5{UHJokP zGB3MFdBV~9XvlNc?eVyk#a&w$b*p797Q1g$q#|G+I;wJ>3)hS*E%5NKq9rI%$Gc!eNByP$PO7_4p~KCM2LoeX?fJRMV`s!@3w@h zh*c=HDSKSC|M!E&%Ixum>}w6m(#cse=UvNw<1U$FRWW5(6}}2(+^)urJTqi9zRV6) z(~uh1m>ufKQV~V)V5I(BcCh(AZs|kQT!cRoy{%ql>E%S47+GIEq1v;@8&%H8I#<%C z%5(D5`)a@KO5Z$wl{Gaf@nQOv&I`^t-2U)4Ew_yv&k1UfYE~6?Z=fRKwMSygTj2|| zdM&+g__L$JVdJTA$KmWOwJH3r5q>jw^p!LGY_apjp+e)2?}oEZr0ZvQ`5Z+NU#pj- zHuu(BL!Er3_V+egU%ad~b*6vY`!j2j;>$SOZ`_0uI{Z2;l?JxjpYX>M9V!~Hi8d*F zrQK$aMZB#se^ERz)*tZO{R}?FS4|>TlZaPW+T6;Qh$rH_<>Rvq%#e}r^AlfdY-9;e zajA%nGujgIPG#l#ohoLxMUx||)QGV_Jgj2Tnn;by2-reO{bF?0=4;cY{YL<7Kd^bMCx1X+};9Ld38-~*xgm=wayAu8Y(Z>UzTqTyK2VN zgj`Db?2hu&rB@Yu3y;)>3tY->cSYSEOYUP%rAn;b#ePMzjsB9xLXStOLhsBRk1A`O z8mZ{KicRI<>OYjED%u;;eOp%*r)IZrPrtFN+mU419PY84x3nR^AN$k!4;`q0&mOQx z?UATGnux{{(L^B88b|~ziCD8rMD5Kg;#N_6fL~Eq*#n8NYVA~kM6^2IIyPkZie0M7 zhzI^skTa$7be#?X>pX}X{sytDd+R^(zmp|OQHyKzuaYwxJE0ysb6PE_|?)~3y zvq!7y%VV|vmbuq=cq?ZW`dvX^UP*0T*x^yH{ol1lmd!r$|E)A(i7dQi!j3OW{I#+2 z`tC|^$Mtg`apw7gu3UeW#}TfprGpOqpforAPWv{Mer4Y=cY>K$k*Ll!T78UmU&Qbv zvMb9htz}n!ulm%A?A;xF8JC7sQ6>0XV6j>Im))_kFKs_jt{nCaeH#as xZ5T*LULUP$ts~dD8wdLO8&|IzSnn9QT7SoY`O*42`!?ik99Z9 { + return text.replace(/\$\((([a-z0-9\-]+?)(~([a-z0-9\-]*?))?)\)/gi, (_match, _g1, name, _g3, animation) => { return ``; }); } diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index 8377d6bcdd..c59a6dc632 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -263,7 +263,7 @@ export class ContextView extends Disposable { const delegate = this.delegate; this.delegate = null; - if (delegate && delegate.onHide) { + if (delegate?.onHide) { delegate.onHide(data); } diff --git a/src/vs/base/browser/ui/countBadge/countBadge.ts b/src/vs/base/browser/ui/countBadge/countBadge.ts index a1e1b58230..da5da53f5b 100644 --- a/src/vs/base/browser/ui/countBadge/countBadge.ts +++ b/src/vs/base/browser/ui/countBadge/countBadge.ts @@ -85,15 +85,15 @@ export class CountBadge { private applyStyles(): void { if (this.element) { - const background = this.badgeBackground ? this.badgeBackground.toString() : null; - const foreground = this.badgeForeground ? this.badgeForeground.toString() : null; - const border = this.badgeBorder ? this.badgeBorder.toString() : null; + const background = this.badgeBackground ? this.badgeBackground.toString() : ''; + const foreground = this.badgeForeground ? this.badgeForeground.toString() : ''; + const border = this.badgeBorder ? this.badgeBorder.toString() : ''; this.element.style.backgroundColor = background; this.element.style.color = foreground; - this.element.style.borderWidth = border ? '1px' : null; - this.element.style.borderStyle = border ? 'solid' : null; + this.element.style.borderWidth = border ? '1px' : ''; + this.element.style.borderStyle = border ? 'solid' : ''; this.element.style.borderColor = border; } } diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts index 62e9a7bf95..a9d75da7aa 100644 --- a/src/vs/base/browser/ui/dialog/dialog.ts +++ b/src/vs/base/browser/ui/dialog/dialog.ts @@ -142,7 +142,9 @@ export class Dialog extends Disposable { let eventHandled = false; if (evt.equals(KeyMod.Shift | KeyCode.Tab) || evt.equals(KeyCode.LeftArrow)) { if (!this.checkboxHasFocus && focusedButton === 0) { - this.checkbox!.domNode.focus(); + if (this.checkbox) { + this.checkbox.domNode.focus(); + } this.checkboxHasFocus = true; } else { focusedButton = (this.checkboxHasFocus ? 0 : focusedButton) + buttonGroup.buttons.length - 1; @@ -154,7 +156,9 @@ export class Dialog extends Disposable { eventHandled = true; } else if (evt.equals(KeyCode.Tab) || evt.equals(KeyCode.RightArrow)) { if (!this.checkboxHasFocus && focusedButton === buttonGroup.buttons.length - 1) { - this.checkbox!.domNode.focus(); + if (this.checkbox) { + this.checkbox.domNode.focus(); + } this.checkboxHasFocus = true; } else { focusedButton = this.checkboxHasFocus ? 0 : focusedButton + 1; @@ -237,10 +241,10 @@ export class Dialog extends Disposable { if (this.styles) { const style = this.styles; - const fgColor = style.dialogForeground ? `${style.dialogForeground}` : null; - const bgColor = style.dialogBackground ? `${style.dialogBackground}` : null; - const shadowColor = style.dialogShadow ? `0 0px 8px ${style.dialogShadow}` : null; - const border = style.dialogBorder ? `1px solid ${style.dialogBorder}` : null; + const fgColor = style.dialogForeground ? `${style.dialogForeground}` : ''; + const bgColor = style.dialogBackground ? `${style.dialogBackground}` : ''; + const shadowColor = style.dialogShadow ? `0 0px 8px ${style.dialogShadow}` : ''; + const border = style.dialogBorder ? `1px solid ${style.dialogBorder}` : ''; if (this.element) { this.element.style.color = fgColor; diff --git a/src/vs/base/browser/ui/dropdown/dropdown.ts b/src/vs/base/browser/ui/dropdown/dropdown.ts index a768840276..278d9106da 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -208,8 +208,8 @@ export interface IDropdownMenuOptions extends IBaseDropdownOptions { export class DropdownMenu extends BaseDropdown { private _contextMenuProvider: IContextMenuProvider; - private _menuOptions: IMenuOptions; - private _actions: ReadonlyArray; + private _menuOptions: IMenuOptions | undefined; + private _actions: ReadonlyArray = []; private actionProvider?: IActionProvider; private menuClassName: string; @@ -222,11 +222,11 @@ export class DropdownMenu extends BaseDropdown { this.menuClassName = options.menuClassName || ''; } - set menuOptions(options: IMenuOptions) { + set menuOptions(options: IMenuOptions | undefined) { this._menuOptions = options; } - get menuOptions(): IMenuOptions { + get menuOptions(): IMenuOptions | undefined { return this._menuOptions; } @@ -256,7 +256,7 @@ export class DropdownMenu extends BaseDropdown { getMenuClassName: () => this.menuClassName, onHide: () => this.onHide(), actionRunner: this.menuOptions ? this.menuOptions.actionRunner : undefined, - anchorAlignment: this.menuOptions.anchorAlignment + anchorAlignment: this.menuOptions ? this.menuOptions.anchorAlignment : AnchorAlignment.LEFT }); } @@ -345,7 +345,11 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { super.setActionContext(newContext); if (this.dropdownMenu) { - this.dropdownMenu.menuOptions.context = newContext; + if (this.dropdownMenu.menuOptions) { + this.dropdownMenu.menuOptions.context = newContext; + } else { + this.dropdownMenu.menuOptions = { context: newContext }; + } } } diff --git a/src/vs/base/browser/ui/findinput/replaceInput.ts b/src/vs/base/browser/ui/findinput/replaceInput.ts index 1fccfc96a7..20925b41b8 100644 --- a/src/vs/base/browser/ui/findinput/replaceInput.ts +++ b/src/vs/base/browser/ui/findinput/replaceInput.ts @@ -123,11 +123,96 @@ export class ReplaceInput extends Widget { this.inputValidationErrorBackground = options.inputValidationErrorBackground; this.inputValidationErrorForeground = options.inputValidationErrorForeground; + const history = options.history || []; const flexibleHeight = !!options.flexibleHeight; const flexibleWidth = !!options.flexibleWidth; const flexibleMaxHeight = options.flexibleMaxHeight; - this.buildDomNode(options.history || [], flexibleHeight, flexibleWidth, flexibleMaxHeight); + this.domNode = document.createElement('div'); + dom.addClass(this.domNode, 'monaco-findInput'); + + this.inputBox = this._register(new HistoryInputBox(this.domNode, this.contextViewProvider, { + ariaLabel: this.label || '', + placeholder: this.placeholder || '', + validationOptions: { + validation: this.validation + }, + inputBackground: this.inputBackground, + inputForeground: this.inputForeground, + inputBorder: this.inputBorder, + inputValidationInfoBackground: this.inputValidationInfoBackground, + inputValidationInfoForeground: this.inputValidationInfoForeground, + inputValidationInfoBorder: this.inputValidationInfoBorder, + inputValidationWarningBackground: this.inputValidationWarningBackground, + inputValidationWarningForeground: this.inputValidationWarningForeground, + inputValidationWarningBorder: this.inputValidationWarningBorder, + inputValidationErrorBackground: this.inputValidationErrorBackground, + inputValidationErrorForeground: this.inputValidationErrorForeground, + inputValidationErrorBorder: this.inputValidationErrorBorder, + history, + flexibleHeight, + flexibleWidth, + flexibleMaxHeight + })); + + this.preserveCase = this._register(new PreserveCaseCheckbox({ + appendTitle: '', + isChecked: false, + inputActiveOptionBorder: this.inputActiveOptionBorder, + inputActiveOptionBackground: this.inputActiveOptionBackground, + })); + this._register(this.preserveCase.onChange(viaKeyboard => { + this._onDidOptionChange.fire(viaKeyboard); + if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { + this.inputBox.focus(); + } + this.validate(); + })); + this._register(this.preserveCase.onKeyDown(e => { + this._onPreserveCaseKeyDown.fire(e); + })); + + if (this._showOptionButtons) { + this.cachedOptionsWidth = this.preserveCase.width(); + } else { + this.cachedOptionsWidth = 0; + } + + // Arrow-Key support to navigate between options + let indexes = [this.preserveCase.domNode]; + this.onkeydown(this.domNode, (event: IKeyboardEvent) => { + if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Escape)) { + let index = indexes.indexOf(document.activeElement); + if (index >= 0) { + let newIndex: number = -1; + if (event.equals(KeyCode.RightArrow)) { + newIndex = (index + 1) % indexes.length; + } else if (event.equals(KeyCode.LeftArrow)) { + if (index === 0) { + newIndex = indexes.length - 1; + } else { + newIndex = index - 1; + } + } + + if (event.equals(KeyCode.Escape)) { + indexes[index].blur(); + } else if (newIndex >= 0) { + indexes[newIndex].focus(); + } + + dom.EventHelper.stop(event, true); + } + } + }); + + + let controls = document.createElement('div'); + controls.className = 'controls'; + controls.style.display = this._showOptionButtons ? 'block' : 'none'; + controls.appendChild(this.preserveCase.domNode); + + this.domNode.appendChild(controls); if (parent) { parent.appendChild(this.domNode); @@ -256,94 +341,6 @@ export class ReplaceInput extends Widget { dom.addClass(this.domNode, 'highlight-' + (this._lastHighlightFindOptions)); } - private buildDomNode(history: string[], flexibleHeight: boolean, flexibleWidth: boolean, flexibleMaxHeight: number | undefined): void { - this.domNode = document.createElement('div'); - dom.addClass(this.domNode, 'monaco-findInput'); - - this.inputBox = this._register(new HistoryInputBox(this.domNode, this.contextViewProvider, { - ariaLabel: this.label || '', - placeholder: this.placeholder || '', - validationOptions: { - validation: this.validation - }, - inputBackground: this.inputBackground, - inputForeground: this.inputForeground, - inputBorder: this.inputBorder, - inputValidationInfoBackground: this.inputValidationInfoBackground, - inputValidationInfoForeground: this.inputValidationInfoForeground, - inputValidationInfoBorder: this.inputValidationInfoBorder, - inputValidationWarningBackground: this.inputValidationWarningBackground, - inputValidationWarningForeground: this.inputValidationWarningForeground, - inputValidationWarningBorder: this.inputValidationWarningBorder, - inputValidationErrorBackground: this.inputValidationErrorBackground, - inputValidationErrorForeground: this.inputValidationErrorForeground, - inputValidationErrorBorder: this.inputValidationErrorBorder, - history, - flexibleHeight, - flexibleWidth, - flexibleMaxHeight - })); - - this.preserveCase = this._register(new PreserveCaseCheckbox({ - appendTitle: '', - isChecked: false, - inputActiveOptionBorder: this.inputActiveOptionBorder, - inputActiveOptionBackground: this.inputActiveOptionBackground, - })); - this._register(this.preserveCase.onChange(viaKeyboard => { - this._onDidOptionChange.fire(viaKeyboard); - if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { - this.inputBox.focus(); - } - this.validate(); - })); - this._register(this.preserveCase.onKeyDown(e => { - this._onPreserveCaseKeyDown.fire(e); - })); - - if (this._showOptionButtons) { - this.cachedOptionsWidth = this.preserveCase.width(); - } else { - this.cachedOptionsWidth = 0; - } - - // Arrow-Key support to navigate between options - let indexes = [this.preserveCase.domNode]; - this.onkeydown(this.domNode, (event: IKeyboardEvent) => { - if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Escape)) { - let index = indexes.indexOf(document.activeElement); - if (index >= 0) { - let newIndex: number = -1; - if (event.equals(KeyCode.RightArrow)) { - newIndex = (index + 1) % indexes.length; - } else if (event.equals(KeyCode.LeftArrow)) { - if (index === 0) { - newIndex = indexes.length - 1; - } else { - newIndex = index - 1; - } - } - - if (event.equals(KeyCode.Escape)) { - indexes[index].blur(); - } else if (newIndex >= 0) { - indexes[newIndex].focus(); - } - - dom.EventHelper.stop(event, true); - } - } - }); - - - let controls = document.createElement('div'); - controls.className = 'controls'; - controls.style.display = this._showOptionButtons ? 'block' : 'none'; - controls.appendChild(this.preserveCase.domNode); - - this.domNode.appendChild(controls); - } - public validate(): void { if (this.inputBox) { this.inputBox.validate(); diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts index b25e448c97..4478710b1f 100644 --- a/src/vs/base/browser/ui/grid/grid.ts +++ b/src/vs/base/browser/ui/grid/grid.ts @@ -9,7 +9,6 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { tail2 as tail, equals } from 'vs/base/common/arrays'; import { orthogonal, IView as IGridViewView, GridView, Sizing as GridViewSizing, Box, IGridViewStyles, IViewSize, IGridViewOptions } from './gridview'; import { Event } from 'vs/base/common/event'; -import { InvisibleSizing } from 'vs/base/browser/ui/splitview/splitview'; export { Orientation, Sizing as GridViewSizing, IViewSize, orthogonal, LayoutPriority } from './gridview'; diff --git a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts index 9732e00fa3..b950a8cf7b 100644 --- a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts +++ b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts @@ -15,15 +15,15 @@ export interface IHighlight { export class HighlightedLabel { private domNode: HTMLElement; - private text: string; - private title: string; - private highlights: IHighlight[]; - private didEverRender: boolean; + private text: string = ''; + private title: string = ''; + private highlights: IHighlight[] = []; + private didEverRender: boolean = false; - constructor(container: HTMLElement, private supportOcticons: boolean) { + constructor(container: HTMLElement, private supportCodicons: boolean) { this.domNode = document.createElement('span'); this.domNode.className = 'monaco-highlighted-label'; - this.didEverRender = false; + container.appendChild(this.domNode); } @@ -65,13 +65,13 @@ export class HighlightedLabel { if (pos < highlight.start) { htmlContent += ''; const substring = this.text.substring(pos, highlight.start); - htmlContent += this.supportOcticons ? renderCodicons(substring) : escape(substring); + htmlContent += this.supportCodicons ? renderCodicons(substring) : escape(substring); htmlContent += ''; pos = highlight.end; } htmlContent += ''; const substring = this.text.substring(highlight.start, highlight.end); - htmlContent += this.supportOcticons ? renderCodicons(substring) : escape(substring); + htmlContent += this.supportCodicons ? renderCodicons(substring) : escape(substring); htmlContent += ''; pos = highlight.end; } @@ -79,7 +79,7 @@ export class HighlightedLabel { if (pos < this.text.length) { htmlContent += ''; const substring = this.text.substring(pos); - htmlContent += this.supportOcticons ? renderCodicons(substring) : escape(substring); + htmlContent += this.supportCodicons ? renderCodicons(substring) : escape(substring); htmlContent += ''; } diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index 8c28a14567..e873ac813d 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -12,7 +12,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; export interface IIconLabelCreationOptions { supportHighlights?: boolean; supportDescriptionHighlights?: boolean; - supportOcticons?: boolean; + supportCodicons?: boolean; } export interface IIconLabelValueOptions { @@ -77,7 +77,7 @@ class FastLabelNode { } this._empty = empty; - this._element.style.marginLeft = empty ? '0' : null; + this._element.style.marginLeft = empty ? '0' : ''; } dispose(): void { @@ -99,14 +99,14 @@ export class IconLabel extends Disposable { this.labelDescriptionContainer = this._register(new FastLabelNode(dom.append(this.domNode.element, dom.$('.monaco-icon-label-description-container')))); - if (options && options.supportHighlights) { - this.labelNode = new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name')), !!options.supportOcticons); + if (options?.supportHighlights) { + this.labelNode = new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name')), !!options.supportCodicons); } else { this.labelNode = this._register(new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name')))); } - if (options && options.supportDescriptionHighlights) { - this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description')), !!options.supportOcticons); + if (options?.supportDescriptionHighlights) { + this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description')), !!options.supportCodicons); } else { this.descriptionNodeFactory = () => this._register(new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description')))); } @@ -129,10 +129,10 @@ export class IconLabel extends Disposable { } this.domNode.className = classes.join(' '); - this.domNode.title = options && options.title ? options.title : ''; + this.domNode.title = options?.title || ''; if (this.labelNode instanceof HighlightedLabel) { - this.labelNode.set(label || '', options ? options.matches : undefined, options && options.title ? options.title : undefined, options && options.labelEscapeNewLines); + this.labelNode.set(label || '', options?.matches, options?.title, options?.labelEscapeNewLines); } else { this.labelNode.textContent = label || ''; } @@ -144,14 +144,14 @@ export class IconLabel extends Disposable { if (this.descriptionNode instanceof HighlightedLabel) { this.descriptionNode.set(description || '', options ? options.descriptionMatches : undefined); - if (options && options.descriptionTitle) { + if (options?.descriptionTitle) { this.descriptionNode.element.title = options.descriptionTitle; } else { this.descriptionNode.element.removeAttribute('title'); } } else { this.descriptionNode.textContent = description || ''; - this.descriptionNode.title = options && options.descriptionTitle ? options.descriptionTitle : ''; + this.descriptionNode.title = options?.descriptionTitle || ''; this.descriptionNode.empty = !description; } } diff --git a/src/vs/base/browser/ui/iconLabel/iconlabel.css b/src/vs/base/browser/ui/iconLabel/iconlabel.css index c962fff7c6..819fe8e62f 100644 --- a/src/vs/base/browser/ui/iconLabel/iconlabel.css +++ b/src/vs/base/browser/ui/iconLabel/iconlabel.css @@ -55,7 +55,7 @@ opacity: 0.75; font-size: 90%; font-weight: 600; - padding: 0 12px 0 5px; + padding: 0 16px 0 5px; margin-left: auto; text-align: center; } @@ -74,4 +74,4 @@ .monaco-list-row.focused.selected .label-description, .monaco-list-row.selected .label-description { opacity: .8; -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index d76ef39448..cba0a2751a 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -201,7 +201,7 @@ export class InputBox extends Widget { const onSelectionChange = Event.filter(domEvent(document, 'selectionchange'), () => { const selection = document.getSelection(); - return !!selection && selection.anchorNode === wrapper; + return selection?.anchorNode === wrapper; }); // from DOM to ScrollableElement @@ -375,7 +375,7 @@ export class InputBox extends Widget { } private updateScrollDimensions(): void { - if (typeof this.cachedContentHeight !== 'number' || typeof this.cachedHeight !== 'number') { + if (typeof this.cachedContentHeight !== 'number' || typeof this.cachedHeight !== 'number' || !this.scrollableElement) { return; } @@ -383,8 +383,8 @@ export class InputBox extends Widget { const height = this.cachedHeight; const scrollTop = this.input.scrollTop; - this.scrollableElement!.setScrollDimensions({ scrollHeight, height }); - this.scrollableElement!.setScrollPosition({ scrollTop }); + this.scrollableElement.setScrollDimensions({ scrollHeight, height }); + this.scrollableElement.setScrollPosition({ scrollTop }); } public showMessage(message: IMessage, force?: boolean): void { @@ -397,7 +397,7 @@ export class InputBox extends Widget { dom.addClass(this.element, this.classForType(message.type)); const styles = this.stylesForType(this.message.type); - this.element.style.border = styles.border ? `1px solid ${styles.border}` : null; + this.element.style.border = styles.border ? `1px solid ${styles.border}` : ''; // ARIA Support let alertText: string; @@ -507,9 +507,9 @@ export class InputBox extends Widget { dom.addClass(spanElement, this.classForType(this.message.type)); const styles = this.stylesForType(this.message.type); - spanElement.style.backgroundColor = styles.background ? styles.background.toString() : null; + spanElement.style.backgroundColor = styles.background ? styles.background.toString() : ''; spanElement.style.color = styles.foreground ? styles.foreground.toString() : null; - spanElement.style.border = styles.border ? `1px solid ${styles.border}` : null; + spanElement.style.border = styles.border ? `1px solid ${styles.border}` : ''; dom.append(div, spanElement); @@ -586,17 +586,17 @@ export class InputBox extends Widget { } protected applyStyles(): void { - const background = this.inputBackground ? this.inputBackground.toString() : null; - const foreground = this.inputForeground ? this.inputForeground.toString() : null; - const border = this.inputBorder ? this.inputBorder.toString() : null; + const background = this.inputBackground ? this.inputBackground.toString() : ''; + const foreground = this.inputForeground ? this.inputForeground.toString() : ''; + const border = this.inputBorder ? this.inputBorder.toString() : ''; this.element.style.backgroundColor = background; this.element.style.color = foreground; this.input.style.backgroundColor = background; this.input.style.color = foreground; - this.element.style.borderWidth = border ? '1px' : null; - this.element.style.borderStyle = border ? 'solid' : null; + this.element.style.borderWidth = border ? '1px' : ''; + this.element.style.borderStyle = border ? 'solid' : ''; this.element.style.borderColor = border; } diff --git a/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts b/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts index 5a3e1f4d52..6c9469315d 100644 --- a/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts +++ b/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts @@ -80,20 +80,20 @@ export class KeybindingLabel { private renderPart(parent: HTMLElement, part: ResolvedKeybindingPart, match: PartMatches | null) { const modifierLabels = UILabelProvider.modifierLabels[this.os]; if (part.ctrlKey) { - this.renderKey(parent, modifierLabels.ctrlKey, Boolean(match && match.ctrlKey), modifierLabels.separator); + this.renderKey(parent, modifierLabels.ctrlKey, Boolean(match?.ctrlKey), modifierLabels.separator); } if (part.shiftKey) { - this.renderKey(parent, modifierLabels.shiftKey, Boolean(match && match.shiftKey), modifierLabels.separator); + this.renderKey(parent, modifierLabels.shiftKey, Boolean(match?.shiftKey), modifierLabels.separator); } if (part.altKey) { - this.renderKey(parent, modifierLabels.altKey, Boolean(match && match.altKey), modifierLabels.separator); + this.renderKey(parent, modifierLabels.altKey, Boolean(match?.altKey), modifierLabels.separator); } if (part.metaKey) { - this.renderKey(parent, modifierLabels.metaKey, Boolean(match && match.metaKey), modifierLabels.separator); + this.renderKey(parent, modifierLabels.metaKey, Boolean(match?.metaKey), modifierLabels.separator); } const keyLabel = part.keyLabel; if (keyLabel) { - this.renderKey(parent, keyLabel, Boolean(match && match.keyCode), ''); + this.renderKey(parent, keyLabel, Boolean(match?.keyCode), ''); } } diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index 7d8c83de33..9f38f5e1ee 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -115,3 +115,19 @@ export class ListError extends Error { super(`ListError [${user}] ${message}`); } } + +export abstract class CachedListVirtualDelegate implements IListVirtualDelegate { + + private cache = new WeakMap(); + + getHeight(element: T): number { + return this.cache.get(element) ?? this.estimateHeight(element); + } + + protected abstract estimateHeight(element: T): number; + abstract getTemplateId(element: T): string; + + setDynamicHeight(element: T, height: number): void { + this.cache.set(element, height); + } +} diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index f52e10f098..716fc514f2 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -155,6 +155,14 @@ export class PagedList implements IDisposable { this.list.scrollTop = scrollTop; } + get scrollLeft(): number { + return this.list.scrollLeft; + } + + set scrollLeft(scrollLeft: number) { + this.list.scrollLeft = scrollLeft; + } + open(indexes: number[], browserEvent?: UIEvent): void { this.list.open(indexes, browserEvent); } diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 9ebe302cf6..3ecb53271a 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -14,8 +14,6 @@ import { ScrollEvent, ScrollbarVisibility, INewScrollDimensions } from 'vs/base/ import { RangeMap, shift } from './rangeMap'; import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListTouchEvent, IListGestureEvent, IListDragEvent, IListDragAndDrop, ListDragOverEffect } from './list'; import { RowCache, IRow } from './rowCache'; -import { isWindows } from 'vs/base/common/platform'; -import * as browser from 'vs/base/browser/browser'; import { ISpliceable } from 'vs/base/common/sequence'; import { memoize } from 'vs/base/common/decorators'; import { Range, IRange } from 'vs/base/common/range'; @@ -179,7 +177,6 @@ export class ListView implements ISpliceable, IDisposable { private additionalScrollHeight: number; private ariaProvider: IAriaProvider; private scrollWidth: number | undefined; - private canUseTranslate3d: boolean | undefined = undefined; private dnd: IListViewDragAndDrop; private canDrop: boolean = false; @@ -236,6 +233,7 @@ export class ListView implements ISpliceable, IDisposable { this.rowsContainer = document.createElement('div'); this.rowsContainer.className = 'monaco-list-rows'; + this.rowsContainer.style.willChange = 'transform'; this.disposables.add(Gesture.addTarget(this.rowsContainer)); this.scrollableElement = this.disposables.add(new ScrollableElement(this.rowsContainer, { @@ -531,33 +529,13 @@ export class ListView implements ISpliceable, IDisposable { } } - const canUseTranslate3d = !isWindows && !browser.isFirefox && browser.getZoomLevel() === 0; - - if (canUseTranslate3d) { - const transform = `translate3d(-${renderLeft}px, -${renderTop}px, 0px)`; - this.rowsContainer.style.transform = transform; - this.rowsContainer.style.webkitTransform = transform; - - if (canUseTranslate3d !== this.canUseTranslate3d) { - this.rowsContainer.style.left = '0'; - this.rowsContainer.style.top = '0'; - } - } else { - this.rowsContainer.style.left = `-${renderLeft}px`; - this.rowsContainer.style.top = `-${renderTop}px`; - - if (canUseTranslate3d !== this.canUseTranslate3d) { - this.rowsContainer.style.transform = ''; - this.rowsContainer.style.webkitTransform = ''; - } - } + this.rowsContainer.style.left = `-${renderLeft}px`; + this.rowsContainer.style.top = `-${renderTop}px`; if (this.horizontalScrolling) { this.rowsContainer.style.width = `${Math.max(scrollWidth, this.renderWidth)}px`; } - this.canUseTranslate3d = canUseTranslate3d; - this.lastRenderTop = renderTop; this.lastRenderHeight = renderHeight; } @@ -686,7 +664,7 @@ export class ListView implements ISpliceable, IDisposable { return scrollPosition.scrollLeft; } - setScrollLeftt(scrollLeft: number): void { + setScrollLeft(scrollLeft: number): void { if (this.scrollableElementUpdateDisposable) { this.scrollableElementUpdateDisposable.dispose(); this.scrollableElementUpdateDisposable = null; diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 693d3016b3..2054db0e1e 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -822,7 +822,7 @@ export interface IListOptions extends IListStyles { readonly automaticKeyboardNavigation?: boolean; readonly keyboardNavigationLabelProvider?: IKeyboardNavigationLabelProvider; readonly keyboardNavigationDelegate?: IKeyboardNavigationDelegate; - readonly ariaRole?: ListAriaRootRole; + readonly ariaRole?: ListAriaRootRole | string; readonly ariaLabel?: string; readonly keyboardSupport?: boolean; readonly multipleSelectionSupport?: boolean; @@ -1198,11 +1198,7 @@ export class List implements ISpliceable, IDisposable { this.view = new ListView(container, virtualDelegate, renderers, viewOptions); - if (typeof _options.ariaRole !== 'string') { - this.view.domNode.setAttribute('role', ListAriaRootRole.TREE); - } else { - this.view.domNode.setAttribute('role', _options.ariaRole); - } + this.updateAriaRole(); this.styleElement = DOM.createStyleSheet(this.view.domNode); @@ -1316,7 +1312,7 @@ export class List implements ISpliceable, IDisposable { } set scrollLeft(scrollLeft: number) { - this.view.setScrollLeftt(scrollLeft); + this.view.setScrollLeft(scrollLeft); } get scrollHeight(): number { @@ -1537,7 +1533,9 @@ export class List implements ISpliceable, IDisposable { const viewItemBottom = elementTop + elementHeight; const wrapperBottom = scrollTop + this.view.renderHeight; - if (elementTop < scrollTop) { + if (elementTop < scrollTop && viewItemBottom >= wrapperBottom) { + // The element is already overflowing the viewport, no-op + } else if (elementTop < scrollTop) { this.view.setScrollTop(elementTop); } else if (viewItemBottom >= wrapperBottom) { this.view.setScrollTop(viewItemBottom - this.view.renderHeight); @@ -1612,10 +1610,19 @@ export class List implements ISpliceable, IDisposable { this.view.domNode.removeAttribute('aria-activedescendant'); } - this.view.domNode.setAttribute('role', 'tree'); + this.updateAriaRole(); + DOM.toggleClass(this.view.domNode, 'element-focused', focus.length > 0); } + private updateAriaRole(): void { + if (typeof this.options.ariaRole !== 'string') { + this.view.domNode.setAttribute('role', ListAriaRootRole.TREE); + } else { + this.view.domNode.setAttribute('role', this.options.ariaRole); + } + } + private _onSelectionChange(): void { const selection = this.selection.get(); diff --git a/src/vs/base/browser/ui/menu/menu.css b/src/vs/base/browser/ui/menu/menu.css index fca338fb5d..b0e3961a14 100644 --- a/src/vs/base/browser/ui/menu/menu.css +++ b/src/vs/base/browser/ui/menu/menu.css @@ -157,7 +157,7 @@ flex-wrap: wrap; } -.fullscreen .menubar { +.fullscreen .menubar:not(.compact) { margin: 0px; padding: 0px 5px; } @@ -176,6 +176,7 @@ .menubar.compact > .menubar-menu-button { width: 100%; height: 100%; + padding: 0px; } .menubar .menubar-menu-items-holder { diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 493330e43e..528be4b3f5 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -16,7 +16,7 @@ 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'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { isLinux, isMacintosh } from 'vs/base/common/platform'; @@ -66,9 +66,6 @@ export class Menu extends ActionBar { private readonly menuDisposables: DisposableStore; private scrollableElement: DomScrollableElement; private menuElement: HTMLElement; - private scrollTopHold: number | undefined; - - private readonly _onScroll: Emitter; constructor(container: HTMLElement, actions: ReadonlyArray, options: IMenuOptions = {}) { addClass(container, 'monaco-menu-container'); @@ -88,8 +85,6 @@ export class Menu extends ActionBar { this.menuElement = menuElement; - this._onScroll = this._register(new Emitter()); - this.actionsList.setAttribute('role', 'menu'); this.actionsList.tabIndex = 0; @@ -113,7 +108,7 @@ export class Menu extends ActionBar { const actions = this.mnemonics.get(key)!; if (actions.length === 1) { - if (actions[0] instanceof SubmenuMenuActionViewItem) { + if (actions[0] instanceof SubmenuMenuActionViewItem && actions[0].container) { this.focusItemByElement(actions[0].container); } @@ -122,7 +117,7 @@ export class Menu extends ActionBar { if (actions.length > 1) { const action = actions.shift(); - if (action) { + if (action && action.container) { this.focusItemByElement(action.container); actions.push(action); } @@ -153,17 +148,11 @@ export class Menu extends ActionBar { let relatedTarget = e.relatedTarget as HTMLElement; if (!isAncestor(relatedTarget, this.domNode)) { this.focusedItem = undefined; - this.scrollTopHold = this.menuElement.scrollTop; this.updateFocus(); e.stopPropagation(); } })); - this._register(addDisposableListener(this.domNode, EventType.MOUSE_UP, e => { - // Absorb clicks in menu dead space https://github.com/Microsoft/vscode/issues/63575 - EventHelper.stop(e, true); - })); - this._register(addDisposableListener(this.actionsList, EventType.MOUSE_OVER, e => { let target = e.target as HTMLElement; if (!target || !isAncestor(target, this.actionsList) || target === this.actionsList) { @@ -176,7 +165,6 @@ export class Menu extends ActionBar { if (hasClass(target, 'action-item')) { const lastFocusedItem = this.focusedItem; - this.scrollTopHold = this.menuElement.scrollTop; this.setFocusedItem(target); if (lastFocusedItem !== this.focusedItem) { @@ -191,8 +179,6 @@ export class Menu extends ActionBar { this.mnemonics = new Map>(); - this.push(actions, { icon: true, label: true, isMenu: true }); - // Scroll Logic this.scrollableElement = this._register(new DomScrollableElement(menuElement, { alwaysConsumeMouseWheel: true, @@ -204,21 +190,17 @@ export class Menu extends ActionBar { })); const scrollElement = this.scrollableElement.getDomNode(); - scrollElement.style.position = null; + scrollElement.style.position = ''; + + this._register(addDisposableListener(scrollElement, EventType.MOUSE_UP, e => { + // Absorb clicks in menu dead space https://github.com/Microsoft/vscode/issues/63575 + // We do this on the scroll element so the scroll bar doesn't dismiss the menu either + e.preventDefault(); + })); menuElement.style.maxHeight = `${Math.max(10, window.innerHeight - container.getBoundingClientRect().top - 30)}px`; - this.menuDisposables.add(this.scrollableElement.onScroll(() => { - this._onScroll.fire(); - }, this)); - - this._register(addDisposableListener(this.menuElement, EventType.SCROLL, (e: ScrollEvent) => { - if (this.scrollTopHold !== undefined) { - this.menuElement.scrollTop = this.scrollTopHold; - this.scrollTopHold = undefined; - } - this.scrollableElement.scanDomNode(); - })); + this.push(actions, { icon: true, label: true, isMenu: true }); container.appendChild(this.scrollableElement.getDomNode()); this.scrollableElement.scanDomNode(); @@ -231,10 +213,10 @@ export class Menu extends ActionBar { style(style: IMenuStyles): void { const container = this.getContainer(); - const fgColor = style.foregroundColor ? `${style.foregroundColor}` : null; - const bgColor = style.backgroundColor ? `${style.backgroundColor}` : null; - const border = style.borderColor ? `2px solid ${style.borderColor}` : null; - const shadow = style.shadowColor ? `0 2px 4px ${style.shadowColor}` : null; + const fgColor = style.foregroundColor ? `${style.foregroundColor}` : ''; + const bgColor = style.backgroundColor ? `${style.backgroundColor}` : ''; + const border = style.borderColor ? `2px solid ${style.borderColor}` : ''; + const shadow = style.shadowColor ? `0 2px 4px ${style.shadowColor}` : ''; container.style.border = border; this.domNode.style.color = fgColor; @@ -254,8 +236,8 @@ export class Menu extends ActionBar { return this.scrollableElement.getDomNode(); } - get onScroll(): Event { - return this._onScroll.event; + get onScroll(): Event { + return this.scrollableElement.onScroll; } get scrollOffset(): number { @@ -295,6 +277,19 @@ export class Menu extends ActionBar { } } + protected updateFocus(fromRight?: boolean): void { + super.updateFocus(fromRight, true); + + if (typeof this.focusedItem !== 'undefined') { + // Workaround for #80047 caused by an issue in chromium + // https://bugs.chromium.org/p/chromium/issues/detail?id=414283 + // When that's fixed, just call this.scrollableElement.scanDomNode() + this.scrollableElement.setScrollPosition({ + scrollTop: Math.round(this.menuElement.scrollTop) + }); + } + } + private doGetActionViewItem(action: IAction, options: IMenuOptions, parentData: ISubMenuData): BaseActionViewItem { if (action instanceof Separator) { return new MenuSeparatorActionViewItem(options.context, action, { icon: true }); @@ -356,17 +351,17 @@ interface IMenuItemOptions extends IActionViewItemOptions { class BaseMenuActionViewItem extends BaseActionViewItem { - public container: HTMLElement; + public container: HTMLElement | undefined; protected options: IMenuItemOptions; - protected item: HTMLElement; + protected item: HTMLElement | undefined; private runOnceToEnableMouseUp: RunOnceScheduler; - private label: HTMLElement; - private check: HTMLElement; - private mnemonic: string; + private label: HTMLElement | undefined; + private check: HTMLElement | undefined; + private mnemonic: string | undefined; private cssClass: string; - protected menuStyle: IMenuStyles; + protected menuStyle: IMenuStyles | undefined; constructor(ctx: any, action: IAction, options: IMenuItemOptions = {}) { options.isMenu = true; @@ -395,6 +390,10 @@ class BaseMenuActionViewItem extends BaseActionViewItem { } this._register(addDisposableListener(this.element, EventType.MOUSE_UP, e => { + if (e.defaultPrevented) { + return; + } + EventHelper.stop(e, true); this.onClick(e); })); @@ -449,13 +448,19 @@ class BaseMenuActionViewItem extends BaseActionViewItem { focus(): void { super.focus(); - this.item.focus(); + + if (this.item) { + this.item.focus(); + } + this.applyStyle(); } updatePositionInSet(pos: number, setSize: number): void { - this.item.setAttribute('aria-posinset', `${pos}`); - this.item.setAttribute('aria-setsize', `${setSize}`); + if (this.item) { + this.item.setAttribute('aria-posinset', `${pos}`); + this.item.setAttribute('aria-setsize', `${setSize}`); + } } updateLabel(): void { @@ -467,7 +472,9 @@ class BaseMenuActionViewItem extends BaseActionViewItem { label = cleanLabel; } - this.label.setAttribute('aria-label', cleanLabel.replace(/&&/g, '&')); + if (this.label) { + this.label.setAttribute('aria-label', cleanLabel.replace(/&&/g, '&')); + } const matches = MENU_MNEMONIC_REGEX.exec(label); @@ -488,13 +495,17 @@ class BaseMenuActionViewItem extends BaseActionViewItem { } label = label.replace(/&&/g, '&'); - this.item.setAttribute('aria-keyshortcuts', (!!matches[1] ? matches[1] : matches[3]).toLocaleLowerCase()); + if (this.item) { + this.item.setAttribute('aria-keyshortcuts', (!!matches[1] ? matches[1] : matches[3]).toLocaleLowerCase()); + } } else { label = label.replace(/&&/g, '&'); } } - this.label.innerHTML = label.trim(); + if (this.label) { + this.label.innerHTML = label.trim(); + } } } @@ -512,23 +523,23 @@ class BaseMenuActionViewItem extends BaseActionViewItem { } } - if (title) { + if (title && this.item) { this.item.title = title; } } updateClass(): void { - if (this.cssClass) { + if (this.cssClass && this.item) { removeClasses(this.item, this.cssClass); } - if (this.options.icon) { + if (this.options.icon && this.label) { this.cssClass = this.getAction().class || ''; addClass(this.label, 'icon'); if (this.cssClass) { addClasses(this.label, this.cssClass); } this.updateEnabled(); - } else { + } else if (this.label) { removeClass(this.label, 'icon'); } } @@ -539,19 +550,27 @@ class BaseMenuActionViewItem extends BaseActionViewItem { removeClass(this.element, 'disabled'); } - removeClass(this.item, 'disabled'); - this.item.tabIndex = 0; + if (this.item) { + removeClass(this.item, 'disabled'); + this.item.tabIndex = 0; + } } else { if (this.element) { addClass(this.element, 'disabled'); } - addClass(this.item, 'disabled'); - removeTabIndexAndUpdateFocus(this.item); + if (this.item) { + addClass(this.item, 'disabled'); + removeTabIndexAndUpdateFocus(this.item); + } } } updateChecked(): void { + if (!this.item) { + return; + } + if (this.getAction().checked) { addClass(this.item, 'checked'); this.item.setAttribute('role', 'menuitemcheckbox'); @@ -563,7 +582,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { } } - getMnemonic(): string { + getMnemonic(): string | undefined { return this.mnemonic; } @@ -575,12 +594,20 @@ 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 ? `thin solid ${this.menuStyle.selectionBorderColor}` : null; + const border = isSelected && this.menuStyle.selectionBorderColor ? `thin solid ${this.menuStyle.selectionBorderColor}` : ''; - this.item.style.color = fgColor ? `${fgColor}` : null; - this.check.style.backgroundColor = fgColor ? `${fgColor}` : null; - this.item.style.backgroundColor = bgColor ? `${bgColor}` : null; - this.container.style.border = border; + if (this.item) { + this.item.style.color = fgColor ? `${fgColor}` : null; + this.item.style.backgroundColor = bgColor ? `${bgColor}` : ''; + } + + if (this.check) { + this.check.style.backgroundColor = fgColor ? `${fgColor}` : ''; + } + + if (this.container) { + this.container.style.border = border; + } } style(style: IMenuStyles): void { @@ -590,11 +617,11 @@ class BaseMenuActionViewItem extends BaseActionViewItem { } class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { - private mysubmenu: Menu | null; + private mysubmenu: Menu | null = null; private submenuContainer: HTMLElement | undefined; - private submenuIndicator: HTMLElement; + private submenuIndicator: HTMLElement | undefined; private readonly submenuDisposables = this._register(new DisposableStore()); - private mouseOver: boolean; + private mouseOver: boolean = false; private showScheduler: RunOnceScheduler; private hideScheduler: RunOnceScheduler; private expandDirection: Direction; @@ -631,11 +658,13 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { return; } - addClass(this.item, 'monaco-submenu-item'); - this.item.setAttribute('aria-haspopup', 'true'); + if (this.item) { + addClass(this.item, 'monaco-submenu-item'); + this.item.setAttribute('aria-haspopup', 'true'); - this.submenuIndicator = append(this.item, $('span.submenu-indicator')); - this.submenuIndicator.setAttribute('aria-hidden', 'true'); + this.submenuIndicator = append(this.item, $('span.submenu-indicator')); + this.submenuIndicator.setAttribute('aria-hidden', 'true'); + } this._register(addDisposableListener(this.element, EventType.KEY_UP, e => { let event = new StandardKeyboardEvent(e); @@ -690,7 +719,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { EventHelper.stop(e, true); this.cleanupExistingSubmenu(false); - this.createSubmenu(false); + this.createSubmenu(true); } private cleanupExistingSubmenu(force: boolean): void { @@ -714,6 +743,12 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { this.submenuContainer = append(this.element, $('div.monaco-submenu')); addClasses(this.submenuContainer, 'menubar-menu-items-holder', 'context-view'); + // Set the top value of the menu container before construction + // This allows the menu constructor to calculate the proper max height + const computedStyles = getComputedStyle(this.parentData.parent.domNode); + const paddingTop = parseFloat(computedStyles.paddingTop || '0') || 0; + this.submenuContainer.style.top = `${this.element.offsetTop - this.parentData.parent.scrollOffset - paddingTop}px`; + this.parentData.submenu = new Menu(this.submenuContainer, this.submenuActions, this.submenuOptions); if (this.menuStyle) { this.parentData.submenu.style(this.menuStyle); @@ -721,8 +756,6 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { const boundingRect = this.element.getBoundingClientRect(); const childBoundingRect = this.submenuContainer.getBoundingClientRect(); - const computedStyles = getComputedStyle(this.parentData.parent.domNode); - const paddingTop = parseFloat(computedStyles.paddingTop || '0') || 0; if (this.expandDirection === Direction.Right) { if (window.innerWidth <= boundingRect.right + childBoundingRect.width) { @@ -793,7 +826,9 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { const isSelected = this.element && hasClass(this.element, 'focused'); const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor; - this.submenuIndicator.style.backgroundColor = fgColor ? `${fgColor}` : null; + if (this.submenuIndicator) { + this.submenuIndicator.style.backgroundColor = fgColor ? `${fgColor}` : ''; + } if (this.parentData.submenu) { this.parentData.submenu.style(this.menuStyle); @@ -818,7 +853,9 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { class MenuSeparatorActionViewItem extends ActionViewItem { style(style: IMenuStyles): void { - this.label.style.borderBottomColor = style.separatorColor ? `${style.separatorColor}` : null; + if (this.label) { + this.label.style.borderBottomColor = style.separatorColor ? `${style.separatorColor}` : ''; + } } } diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 5b17c6a378..0d60556918 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -20,6 +20,7 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { asArray } from 'vs/base/common/arrays'; import { ScanCodeUtils, ScanCode } from 'vs/base/common/scanCode'; import { isMacintosh } from 'vs/base/common/platform'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; const $ = DOM.$; @@ -55,7 +56,7 @@ export class MenuBar extends Disposable { actions?: ReadonlyArray; }[]; - private overflowMenu: { + private overflowMenu!: { buttonElement: HTMLElement; titleElement: HTMLElement; label: string; @@ -72,22 +73,22 @@ export class MenuBar extends Disposable { private menuUpdater: RunOnceScheduler; // Input-related - private _mnemonicsInUse: boolean; - private openedViaKeyboard: boolean; - private awaitingAltRelease: boolean; - private ignoreNextMouseUp: boolean; + private _mnemonicsInUse: boolean = false; + private openedViaKeyboard: boolean = false; + private awaitingAltRelease: boolean = false; + private ignoreNextMouseUp: boolean = false; private mnemonics: Map; - private updatePending: boolean; + private updatePending: boolean = false; private _focusState: MenubarState; private actionRunner: IActionRunner; private readonly _onVisibilityChange: Emitter; private readonly _onFocusStateChange: Emitter; - private numMenusShown: number; - private menuStyle: IMenuStyles; - private overflowLayoutScheduled: IDisposable | null; + private numMenusShown: number = 0; + private menuStyle: IMenuStyles | undefined; + private overflowLayoutScheduled: IDisposable | null = null; constructor(private container: HTMLElement, private options: IMenuBarOptions = {}) { super(); @@ -252,7 +253,14 @@ export class MenuBar extends Disposable { e.stopPropagation(); })); - this._register(DOM.addDisposableListener(buttonElement, DOM.EventType.MOUSE_DOWN, (e) => { + this._register(DOM.addDisposableListener(buttonElement, DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => { + // Ignore non-left-click + const mouseEvent = new StandardMouseEvent(e); + if (!mouseEvent.leftButton) { + e.preventDefault(); + return; + } + if (!this.isOpen) { // Open the menu with mouse down and ignore the following mouse up event this.ignoreNextMouseUp = true; @@ -266,6 +274,10 @@ export class MenuBar extends Disposable { })); this._register(DOM.addDisposableListener(buttonElement, DOM.EventType.MOUSE_UP, (e) => { + if (e.defaultPrevented) { + return; + } + if (!this.ignoreNextMouseUp) { if (this.isFocused) { this.onMenuTriggered(menuIndex, true); @@ -337,6 +349,13 @@ export class MenuBar extends Disposable { })); this._register(DOM.addDisposableListener(buttonElement, DOM.EventType.MOUSE_DOWN, (e) => { + // Ignore non-left-click + const mouseEvent = new StandardMouseEvent(e); + if (!mouseEvent.leftButton) { + e.preventDefault(); + return; + } + if (!this.isOpen) { // Open the menu with mouse down and ignore the following mouse up event this.ignoreNextMouseUp = true; @@ -350,6 +369,10 @@ export class MenuBar extends Disposable { })); this._register(DOM.addDisposableListener(buttonElement, DOM.EventType.MOUSE_UP, (e) => { + if (e.defaultPrevented) { + return; + } + if (!this.ignoreNextMouseUp) { if (this.isFocused) { this.onMenuTriggered(MenuBar.OVERFLOW_INDEX, true); @@ -462,9 +485,11 @@ export class MenuBar extends Disposable { this.overflowMenu.actions.push(new SubmenuAction(this.menuCache[idx].label, this.menuCache[idx].actions || [])); } - DOM.removeNode(this.overflowMenu.buttonElement); - this.container.insertBefore(this.overflowMenu.buttonElement, this.menuCache[this.numMenusShown].buttonElement); - this.overflowMenu.buttonElement.style.visibility = 'visible'; + if (this.overflowMenu.buttonElement.nextElementSibling !== this.menuCache[this.numMenusShown].buttonElement) { + DOM.removeNode(this.overflowMenu.buttonElement); + this.container.insertBefore(this.overflowMenu.buttonElement, this.menuCache[this.numMenusShown].buttonElement); + this.overflowMenu.buttonElement.style.visibility = 'visible'; + } } else { DOM.removeNode(this.overflowMenu.buttonElement); this.container.appendChild(this.overflowMenu.buttonElement); @@ -913,7 +938,9 @@ export class MenuBar extends Disposable { }; let menuWidget = this._register(new Menu(menuHolder, customMenu.actions, menuOptions)); - menuWidget.style(this.menuStyle); + if (this.menuStyle) { + menuWidget.style(this.menuStyle); + } this._register(menuWidget.onDidCancel(() => { this.focusState = MenubarState.FOCUSED; diff --git a/src/vs/base/browser/ui/octiconLabel/octiconLabel.mock.ts b/src/vs/base/browser/ui/octiconLabel/octiconLabel.mock.ts deleted file mode 100644 index 17677a0303..0000000000 --- a/src/vs/base/browser/ui/octiconLabel/octiconLabel.mock.ts +++ /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. - *--------------------------------------------------------------------------------------------*/ - -import { escape } from 'vs/base/common/strings'; - -export function renderOcticons(text: string): string { - return escape(text); -} - -export class OcticonLabel { - - private _container: HTMLElement; - - constructor(container: HTMLElement) { - this._container = container; - } - - set text(text: string) { - this._container.innerHTML = renderOcticons(text || ''); - } - -} diff --git a/src/vs/base/browser/ui/octiconLabel/octiconLabel.ts b/src/vs/base/browser/ui/octiconLabel/octiconLabel.ts deleted file mode 100644 index 32174a6db8..0000000000 --- a/src/vs/base/browser/ui/octiconLabel/octiconLabel.ts +++ /dev/null @@ -1,33 +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!./octicons/octicons'; -import 'vs/css!./octicons/octicons-animations'; -import { escape } from 'vs/base/common/strings'; - -function expand(text: string): string { - return text.replace(/\$\(((.+?)(~(.*?))?)\)/g, (_match, _g1, name, _g3, animation) => { - return ``; - }); -} - -export function renderOcticons(label: string): string { - return expand(escape(label)); -} - -export class OcticonLabel { - - constructor( - private readonly _container: HTMLElement - ) { } - - set text(text: string) { - this._container.innerHTML = renderOcticons(text || ''); - } - - set title(title: string) { - this._container.title = title; - } -} diff --git a/src/vs/base/browser/ui/octiconLabel/octicons/octicons-animations.css b/src/vs/base/browser/ui/octiconLabel/octicons/octicons-animations.css deleted file mode 100644 index 265454bcd3..0000000000 --- a/src/vs/base/browser/ui/octiconLabel/octicons/octicons-animations.css +++ /dev/null @@ -1,14 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -@keyframes octicon-spin { - 100% { - transform:rotate(360deg); - } -} - -.octicon-animation-spin { - animation: octicon-spin 1.5s linear infinite; -} diff --git a/src/vs/base/browser/ui/octiconLabel/octicons/octicons.css b/src/vs/base/browser/ui/octiconLabel/octicons/octicons.css deleted file mode 100644 index 154a225158..0000000000 --- a/src/vs/base/browser/ui/octiconLabel/octicons/octicons.css +++ /dev/null @@ -1,251 +0,0 @@ -@font-face { - font-family: "octicons"; - src: url("./octicons.ttf?1829db8570ee0fa5a4bef3bb41d5f62e") format("truetype"); -} - -.octicon, .mega-octicon { - font: normal normal normal 16px/1 octicons; - display: inline-block; - text-decoration: none; - text-rendering: auto; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.mega-octicon { font-size: 32px; } - - - -.octicon-alert:before { content: "\f02d" } -.octicon-arrow-down:before { content: "\f03f" } -.octicon-arrow-left:before { content: "\f040" } -.octicon-arrow-right:before { content: "\f03e" } -.octicon-arrow-small-down:before { content: "\f0a0" } -.octicon-arrow-small-left:before { content: "\f0a1" } -.octicon-arrow-small-right:before { content: "\f071" } -.octicon-arrow-small-up:before { content: "\f09f" } -.octicon-arrow-up:before { content: "\f03d" } -.octicon-beaker:before { content: "\f0dd" } -.octicon-bell:before { content: "\f0de" } -.octicon-bold:before { content: "\f282" } -.octicon-book:before { content: "\f007" } -.octicon-bookmark:before { content: "\f07b" } -.octicon-briefcase:before { content: "\f0d3" } -.octicon-broadcast:before { content: "\f048" } -.octicon-browser:before { content: "\f0c5" } -.octicon-bug:before { content: "\f091" } -.octicon-calendar:before { content: "\f068" } -.octicon-check:before { content: "\f03a" } -.octicon-checklist:before { content: "\f076" } -.octicon-chevron-down:before { content: "\f0a3" } -.octicon-chevron-left:before { content: "\f0a4" } -.octicon-chevron-right:before { content: "\f078" } -.octicon-chevron-up:before { content: "\f0a2" } -.octicon-circle-slash:before { content: "\f084" } -.octicon-circuit-board:before { content: "\f0d6" } -.octicon-clippy:before { content: "\f035" } -.octicon-clock:before { content: "\f046" } -.octicon-clone:before { content: "\f0dc" } -.octicon-cloud-download:before { content: "\f00b" } -.octicon-cloud-upload:before { content: "\f00c" } -.octicon-code:before { content: "\f05f" } -.octicon-color-mode:before { content: "\f065" } -.octicon-comment-add:before { content: "\f02b" } -.octicon-comment-discussion:before { content: "\f04f" } -.octicon-comment:before { content: "\f02b" } -.octicon-credit-card:before { content: "\f045" } -.octicon-dash:before { content: "\f0ca" } -.octicon-dashboard:before { content: "\f07d" } -.octicon-database:before { content: "\f096" } -.octicon-desktop-download:before { content: "\f0dc" } -.octicon-device-camera-video:before { content: "\f057" } -.octicon-device-camera:before { content: "\f056" } -.octicon-device-desktop:before { content: "\f27c" } -.octicon-device-mobile:before { content: "\f038" } -.octicon-diff-added:before { content: "\f06b" } -.octicon-diff-ignored:before { content: "\f099" } -.octicon-diff-modified:before { content: "\f06d" } -.octicon-diff-removed:before { content: "\f06c" } -.octicon-diff-renamed:before { content: "\f06e" } -.octicon-diff:before { content: "\f04d" } -.octicon-ellipsis:before { content: "\f09a" } -.octicon-eye-unwatch:before { content: "\f04e" } -.octicon-eye-watch:before { content: "\f04e" } -.octicon-eye:before { content: "\f04e" } -.octicon-file-add:before { content: "\f05d" } -.octicon-file-binary:before { content: "\f094" } -.octicon-file-code:before { content: "\f010" } -.octicon-file-directory-create:before { content: "\f05d" } -.octicon-file-directory:before { content: "\f016" } -.octicon-file-media:before { content: "\f012" } -.octicon-file-pdf:before { content: "\f014" } -.octicon-file-submodule:before { content: "\f017" } -.octicon-file-symlink-directory:before { content: "\f0b1" } -.octicon-file-symlink-file:before { content: "\f0b0" } -.octicon-file-text:before { content: "\f283" } -.octicon-file-zip:before { content: "\f013" } -.octicon-file:before { content: "\f283" } -.octicon-flame:before { content: "\f0d2" } -.octicon-fold:before { content: "\f0cc" } -.octicon-gear:before { content: "\f02f" } -.octicon-gift:before { content: "\f042" } -.octicon-gist-fork:before { content: "\f002" } -.octicon-gist-new:before { content: "\f05d" } -.octicon-gist-private:before { content: "\f06a" } -.octicon-gist-secret:before { content: "\f08c" } -.octicon-gist:before { content: "\f00e" } -.octicon-git-branch-create:before { content: "\f020" } -.octicon-git-branch-delete:before { content: "\f020" } -.octicon-git-branch:before { content: "\f020" } -.octicon-git-commit:before { content: "\f01f" } -.octicon-git-compare:before { content: "\f0ac" } -.octicon-git-fork-private:before { content: "\f06a" } -.octicon-git-merge:before { content: "\f023" } -.octicon-git-pull-request-abandoned:before { content: "\f009" } -.octicon-git-pull-request:before { content: "\f009" } -.octicon-globe:before { content: "\f0b6" } -.octicon-grabber:before { content: "\f284" } -.octicon-graph:before { content: "\f043" } -.octicon-heart:before { content: "\2665" } -.octicon-history:before { content: "\f07e" } -.octicon-home:before { content: "\f08d" } -.octicon-horizontal-rule:before { content: "\f070" } -.octicon-hubot:before { content: "\f09d" } -.octicon-inbox:before { content: "\f0cf" } -.octicon-info:before { content: "\f059" } -.octicon-issue-closed:before { content: "\f028" } -.octicon-issue-opened:before { content: "\f026" } -.octicon-issue-reopened:before { content: "\f027" } -.octicon-italic:before { content: "\f285" } -.octicon-jersey:before { content: "\f019" } -.octicon-kebab-horizontal:before { content: "\f286" } -.octicon-kebab-vertical:before { content: "\f287" } -.octicon-key:before { content: "\f049" } -.octicon-keyboard:before { content: "\f00d" } -.octicon-law:before { content: "\f0d8" } -.octicon-light-bulb:before { content: "\f000" } -.octicon-link-external:before { content: "\f07f" } -.octicon-link:before { content: "\f05c" } -.octicon-list-ordered:before { content: "\f062" } -.octicon-list-unordered:before { content: "\f061" } -.octicon-location:before { content: "\f060" } -.octicon-lock:before { content: "\f06a" } -.octicon-log-in:before { content: "\f036" } -.octicon-log-out:before { content: "\f032" } -.octicon-mail-read:before { content: "\f03c" } -.octicon-mail-reply:before { content: "\f28c" } -.octicon-mail:before { content: "\f03b" } -.octicon-logo-gist:before { content: "\f00a" } -.octicon-logo-github:before { content: "\f00a" } -.octicon-mark-github:before { content: "\f00a" } -.octicon-markdown:before { content: "\f0c9" } -.octicon-megaphone:before { content: "\f077" } -.octicon-mention:before { content: "\f0be" } -.octicon-microscope:before { content: "\f0dd" } -.octicon-milestone:before { content: "\f075" } -.octicon-mirror-private:before { content: "\f06a" } -.octicon-mirror-public:before { content: "\f024" } -.octicon-mirror:before { content: "\f024" } -.octicon-mortar-board:before { content: "\f0d7" } -.octicon-mute:before { content: "\f080" } -.octicon-no-newline:before { content: "\f09c" } -.octicon-note:before { content: "\f289" } -.octicon-octoface:before { content: "\f008" } -.octicon-organization:before { content: "\f037" } -.octicon-organization-filled:before { content: "\f037" } -.octicon-organization-outline:before { content: "\f037" } -.octicon-package:before { content: "\f0c4" } -.octicon-paintcan:before { content: "\f0d1" } -.octicon-pencil:before { content: "\f058" } -.octicon-person-add:before { content: "\f018" } -.octicon-person-follow:before { content: "\f018" } -.octicon-person:before { content: "\f018" } -.octicon-person-filled:before { content: "\f018" } -.octicon-person-outline:before { content: "\f018" } -.octicon-pin:before { content: "\f041" } -.octicon-plug:before { content: "\f0d4" } -.octicon-plus-small:before { content: "\f28a" } -.octicon-plus:before { content: "\f05d" } -.octicon-primitive-dot:before { content: "\f052" } -.octicon-primitive-square:before { content: "\f053" } -.octicon-project:before { content: "\f28b" } -.octicon-pulse:before { content: "\f085" } -.octicon-question:before { content: "\f02c" } -.octicon-quote:before { content: "\f063" } -.octicon-radio-tower:before { content: "\f030" } -.octicon-remove-close:before { content: "\f081" } -.octicon-reply:before { content: "\f28c" } -.octicon-repo-clone:before { content: "\f04c" } -.octicon-repo-create:before { content: "\f05d" } -.octicon-repo-delete:before { content: "\f001" } -.octicon-repo-force-push:before { content: "\f04a" } -.octicon-repo-forked:before { content: "\f002" } -.octicon-repo-pull:before { content: "\f006" } -.octicon-repo-push:before { content: "\f005" } -.octicon-repo-sync:before { content: "\f087" } -.octicon-repo:before { content: "\f001" } -.octicon-report:before { content: "\f28d" } -.octicon-rocket:before { content: "\f033" } -.octicon-rss:before { content: "\f034" } -.octicon-ruby:before { content: "\f047" } -.octicon-screen-full:before { content: "\f066" } -.octicon-screen-normal:before { content: "\f067" } -.octicon-search-save:before { content: "\f02e" } -.octicon-search:before { content: "\f02e" } -.octicon-server:before { content: "\f097" } -.octicon-settings:before { content: "\f07c" } -.octicon-shield:before { content: "\f0e1" } -.octicon-sign-in:before { content: "\f036" } -.octicon-sign-out:before { content: "\f032" } -.octicon-smiley:before { content: "\26b2" } -.octicon-squirrel:before { content: "\f0b2" } -.octicon-star-add:before { content: "\f02a" } -.octicon-star-delete:before { content: "\f02a" } -.octicon-star:before { content: "\f02a" } -.octicon-stop:before { content: "\f08f" } -.octicon-sync:before { content: "\f087" } -.octicon-tag-add:before { content: "\f015" } -.octicon-tag-remove:before { content: "\f015" } -.octicon-tag:before { content: "\f015" } -.octicon-tasklist:before { content: "\f27e" } -.octicon-telescope:before { content: "\f088" } -.octicon-terminal:before { content: "\f0c8" } -.octicon-text-size:before { content: "\f27f" } -.octicon-three-bars:before { content: "\f05e" } -.octicon-thumbsdown:before { content: "\f0db" } -.octicon-thumbsup:before { content: "\f0da" } -.octicon-tools:before { content: "\f031" } -.octicon-trashcan:before { content: "\f0d0" } -.octicon-triangle-down:before { content: "\f05b" } -.octicon-triangle-left:before { content: "\f044" } -.octicon-triangle-right:before { content: "\f05a" } -.octicon-triangle-up:before { content: "\f0aa" } -.octicon-unfold:before { content: "\f039" } -.octicon-unmute:before { content: "\f0ba" } -.octicon-unverified:before { content: "\f280" } -.octicon-verified:before { content: "\f281" } -.octicon-versions:before { content: "\f064" } -.octicon-watch:before { content: "\f0e0" } -.octicon-x:before { content: "\f081" } -.octicon-zap:before { content: "\26a1" } -.octicon-error:before { content: "\26b1" } -.octicon-eye-closed:before { content: "\26a3" } -.octicon-fold-down:before { content: "\26a4" } -.octicon-fold-up:before { content: "\26a5" } -.octicon-github-action:before { content: "\26a6" } -.octicon-info-outline:before { content: "\26a7" } -.octicon-play:before { content: "\26a8" } -.octicon-remote:before { content: "\26a9" } -.octicon-request-changes:before { content: "\26aa" } -.octicon-smiley-outline:before { content: "\26b2" } -.octicon-warning:before { content: "\f02d" } -.octicon-controls:before { content: "\26ad" } -.octicon-event:before { content: "\26ae" } -.octicon-record-keys:before { content: "\26af" } -.octicon-Vector:before { content: "\f101" } -.octicon-archive:before { content: "\f102" } -.octicon-arrow-both:before { content: "\f103" } diff --git a/src/vs/base/browser/ui/octiconLabel/octicons/octicons.ttf b/src/vs/base/browser/ui/octiconLabel/octicons/octicons.ttf deleted file mode 100644 index 90550092a65dcf320264753b16d5ac9217e37f6e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35428 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�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 G{{9cd(CC%` diff --git a/src/vs/base/browser/ui/progressbar/progressbar.ts b/src/vs/base/browser/ui/progressbar/progressbar.ts index da1f7fe5c6..a6a1e68e5b 100644 --- a/src/vs/base/browser/ui/progressbar/progressbar.ts +++ b/src/vs/base/browser/ui/progressbar/progressbar.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./progressbar'; -import * as assert from 'vs/base/common/assert'; import { Disposable } from 'vs/base/common/lifecycle'; import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; @@ -154,9 +153,7 @@ export class ProgressBar extends Disposable { * Tells the progress bar that an increment of work has been completed. */ worked(value: number): ProgressBar { - value = Number(value); - assert.ok(!isNaN(value), 'Value is not a number'); - value = Math.max(1, value); + value = Math.max(1, Number(value)); return this.doSetWorked(this.workedVal + value); } @@ -165,16 +162,13 @@ export class ProgressBar extends Disposable { * Tells the progress bar the total amount of work that has been completed. */ setWorked(value: number): ProgressBar { - value = Number(value); - assert.ok(!isNaN(value), 'Value is not a number'); - value = Math.max(1, value); + value = Math.max(1, Number(value)); return this.doSetWorked(value); } private doSetWorked(value: number): ProgressBar { - assert.ok(isNumber(this.totalWork), 'Total work not set'); - const totalWork = this.totalWork!; + const totalWork = this.totalWork || 100; this.workedVal = value; this.workedVal = Math.min(totalWork, this.workedVal); @@ -227,7 +221,7 @@ export class ProgressBar extends Disposable { protected applyStyles(): void { if (this.bit) { - const background = this.progressBarBackground ? this.progressBarBackground.toString() : null; + const background = this.progressBarBackground ? this.progressBarBackground.toString() : ''; this.bit.style.backgroundColor = background; } diff --git a/src/vs/base/browser/ui/selectBox/selectBox.ts b/src/vs/base/browser/ui/selectBox/selectBox.ts index bba7797587..44122e5f7b 100644 --- a/src/vs/base/browser/ui/selectBox/selectBox.ts +++ b/src/vs/base/browser/ui/selectBox/selectBox.ts @@ -83,7 +83,7 @@ export class SelectBox extends Widget implements ISelectBoxDelegate { super(); // Default to native SelectBox for OSX unless overridden - if (isMacintosh && !(selectBoxOptions && selectBoxOptions.useCustomDrawn)) { + if (isMacintosh && !selectBoxOptions?.useCustomDrawn) { this.selectBoxDelegate = new SelectBoxNative(options, selected, styles, selectBoxOptions); } else { this.selectBoxDelegate = new SelectBoxList(options, selected, contextViewProvider, styles, selectBoxOptions); diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index d1d28c74b0..e6ae459cfd 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -381,22 +381,22 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi // Style parent select // {{SQL CARBON EDIT}} - let background: Color | undefined = undefined; - let foreground: Color | undefined = undefined; - let border: Color | undefined = undefined; + let background = ''; + let foreground = ''; + let border = ''; if (this.selectElement) { if (this.selectElement.disabled) { - background = (this.styles).disabledSelectBackground; - foreground = (this.styles).disabledSelectForeground; + background = (this.styles).disabledSelectBackground ? (this.styles).disabledSelectBackground.toString() : ''; + foreground = (this.styles).disabledSelectForeground ? (this.styles).disabledSelectForeground.toString() : ''; } else { - background = this.styles.selectBackground; - foreground = this.styles.selectForeground; - border = this.styles.selectBorder; + background = this.styles.selectBackground ? this.styles.selectBackground.toString() : ''; + foreground = this.styles.selectForeground ? this.styles.selectForeground.toString() : ''; + border = this.styles.selectBorder ? this.styles.selectBorder.toString() : ''; } - this.selectElement.style.backgroundColor = background ? background.toString() : null; - this.selectElement.style.color = foreground ? foreground.toString() : null; - this.selectElement.style.borderColor = border ? border.toString() : null; + this.selectElement.style.backgroundColor = background; + this.selectElement.style.color = foreground; + this.selectElement.style.borderColor = border; } // Style drop down select list (non-native mode only) @@ -408,10 +408,10 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi private styleList() { if (this.selectList) { - let background = this.styles.selectBackground ? this.styles.selectBackground.toString() : null; + const background = this.styles.selectBackground ? this.styles.selectBackground.toString() : ''; this.selectList.style({}); - let listBackground = this.styles.selectListBackground ? this.styles.selectListBackground.toString() : background; + const listBackground = this.styles.selectListBackground ? this.styles.selectListBackground.toString() : background; this.selectDropDownListContainer.style.backgroundColor = listBackground; this.selectionDetailsPane.style.backgroundColor = listBackground; const optionsBorder = this.styles.focusBorder ? this.styles.focusBorder.toString() : ''; diff --git a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts index 0d740f729e..09b3f34254 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts @@ -148,9 +148,9 @@ export class SelectBoxNative extends Disposable implements ISelectBoxDelegate { // Style native select if (this.selectElement) { - const background = this.styles.selectBackground ? this.styles.selectBackground.toString() : null; - const foreground = this.styles.selectForeground ? this.styles.selectForeground.toString() : null; - const border = this.styles.selectBorder ? this.styles.selectBorder.toString() : null; + const background = this.styles.selectBackground ? this.styles.selectBackground.toString() : ''; + const foreground = this.styles.selectForeground ? this.styles.selectForeground.toString() : ''; + const border = this.styles.selectBorder ? this.styles.selectBorder.toString() : ''; this.selectElement.style.backgroundColor = background; this.selectElement.style.color = foreground; diff --git a/src/vs/base/browser/ui/splitview/panelview.css b/src/vs/base/browser/ui/splitview/panelview.css index acd55ea888..9ded1c239b 100644 --- a/src/vs/base/browser/ui/splitview/panelview.css +++ b/src/vs/base/browser/ui/splitview/panelview.css @@ -32,6 +32,7 @@ justify-content: center; transform-origin: center; color: inherit; + flex-shrink: 0; } .monaco-panel-view .panel > .panel-header.expanded > .twisties::before { diff --git a/src/vs/base/browser/ui/splitview/panelview.ts b/src/vs/base/browser/ui/splitview/panelview.ts index fad62d8895..9e3bd31ca4 100644 --- a/src/vs/base/browser/ui/splitview/panelview.ts +++ b/src/vs/base/browser/ui/splitview/panelview.ts @@ -9,7 +9,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { $, append, addClass, removeClass, toggleClass, trackFocus, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { $, append, addClass, removeClass, toggleClass, trackFocus } from 'vs/base/browser/dom'; import { firstIndex } from 'vs/base/common/arrays'; import { Color, RGBA } from 'vs/base/common/color'; import { SplitView, IView } from './splitview'; @@ -58,6 +58,9 @@ export abstract class Panel extends Disposable implements IView { private readonly _onDidChange = this._register(new Emitter()); readonly onDidChange: Event = this._onDidChange.event; + private readonly _onDidChangeExpansionState = this._register(new Emitter()); + readonly onDidChangeExpansionState: Event = this._onDidChangeExpansionState.event; + get draggableElement(): HTMLElement { return this.header; } @@ -144,6 +147,7 @@ export abstract class Panel extends Disposable implements IView { }, 200); } + this._onDidChangeExpansionState.fire(expanded); this._onDidChange.fire(expanded ? this.expandedSize : undefined); return true; } @@ -225,8 +229,8 @@ export abstract class Panel extends Disposable implements IView { this.header.setAttribute('aria-expanded', String(expanded)); this.header.style.color = this.styles.headerForeground ? this.styles.headerForeground.toString() : null; - this.header.style.backgroundColor = this.styles.headerBackground ? this.styles.headerBackground.toString() : null; - this.header.style.borderTop = this.styles.headerBorder ? `1px solid ${this.styles.headerBorder}` : null; + this.header.style.backgroundColor = this.styles.headerBackground ? this.styles.headerBackground.toString() : ''; + this.header.style.borderTop = this.styles.headerBorder ? `1px solid ${this.styles.headerBorder}` : ''; this._dropBackground = this.styles.dropBackground; } @@ -336,7 +340,7 @@ class PanelDraggable extends Disposable { backgroundColor = (this.panel.dropBackground || PanelDraggable.DefaultDragOverBackgroundColor).toString(); } - this.panel.dropTargetElement.style.backgroundColor = backgroundColor; + this.panel.dropTargetElement.style.backgroundColor = backgroundColor || ''; } } @@ -391,13 +395,7 @@ export class PanelView extends Disposable { addPanel(panel: Panel, size: number, index = this.splitview.length): void { const disposables = new DisposableStore(); - - // https://github.com/Microsoft/vscode/issues/59950 - let shouldAnimate = false; - disposables.add(scheduleAtNextAnimationFrame(() => shouldAnimate = true)); - - disposables.add(Event.filter(panel.onDidChange, () => shouldAnimate) - (this.setupAnimation, this)); + panel.onDidChangeExpansionState(this.setupAnimation, this, disposables); const panelItem = { panel, disposable: disposables }; this.panelItems.splice(index, 0, panelItem); diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index c958f75f32..1ef430d158 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -129,9 +129,9 @@ export class ToolBar extends Disposable { } private getKeybindingLabel(action: IAction): string | undefined { - const key = this.lookupKeybindings && this.options.getKeyBinding ? this.options.getKeyBinding(action) : undefined; + const key = this.lookupKeybindings ? this.options.getKeyBinding?.(action) : undefined; - return withNullAsUndefined(key && key.getLabel()); + return withNullAsUndefined(key?.getLabel()); } addPrimaryAction(primaryAction: IAction): () => void { diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index afd9a7d070..0a9e363f68 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -238,7 +238,7 @@ class EventCollection implements Collection { class TreeRenderer implements IListRenderer, ITreeListTemplateData> { - private static DefaultIndent = 8; + private static readonly DefaultIndent = 8; readonly templateId: string; private renderedElements = new Map>(); @@ -250,7 +250,7 @@ class TreeRenderer implements IListRenderer private activeIndentNodes = new Set>(); private indentGuidesDisposable: IDisposable = Disposable.None; - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); constructor( private renderer: ITreeRenderer, @@ -458,7 +458,7 @@ class TreeRenderer implements IListRenderer this.renderedNodes.clear(); this.renderedElements.clear(); this.indentGuidesDisposable.dispose(); - this.disposables = dispose(this.disposables); + dispose(this.disposables); } } @@ -471,7 +471,7 @@ class TypeFilter implements ITreeFilter, IDisposable { private _pattern: string = ''; private _lowercasePattern: string = ''; - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); set pattern(pattern: string) { this._pattern = pattern; @@ -546,7 +546,7 @@ class TypeFilter implements ITreeFilter, IDisposable { } dispose(): void { - this.disposables = dispose(this.disposables); + dispose(this.disposables); } } @@ -581,8 +581,8 @@ class TypeFilterController implements IDisposable { private readonly _onDidChangePattern = new Emitter(); readonly onDidChangePattern = this._onDidChangePattern.event; - private enabledDisposables: IDisposable[] = []; - private disposables: IDisposable[] = []; + private readonly enabledDisposables = new DisposableStore(); + private readonly disposables = new DisposableStore(); constructor( private tree: AbstractTree, @@ -657,6 +657,7 @@ class TypeFilterController implements IDisposable { const onKeyDown = Event.chain(domEvent(this.view.getHTMLElement(), 'keydown')) .filter(e => !isInputElement(e.target as HTMLElement) || e.target === this.filterOnTypeDomNode) + .filter(e => e.key !== 'Dead') .map(e => new StandardKeyboardEvent(e)) .filter(this.keyboardNavigationEventFilter || (() => true)) .filter(() => this.automaticKeyboardNavigation || this.triggered) @@ -682,7 +683,7 @@ class TypeFilterController implements IDisposable { } this.domNode.remove(); - this.enabledDisposables = dispose(this.enabledDisposables); + this.enabledDisposables.clear(); this.tree.refilter(); this.render(); this._enabled = false; @@ -744,7 +745,7 @@ class TypeFilterController implements IDisposable { const containerWidth = container.clientWidth; const midContainerWidth = containerWidth / 2; const width = this.domNode.clientWidth; - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); let positionClassName = this.positionClassName; const updatePosition = () => { @@ -780,8 +781,8 @@ class TypeFilterController implements IDisposable { const onDragEnd = () => { this.positionClassName = positionClassName; this.domNode.className = `monaco-list-type-filter ${this.positionClassName}`; - this.domNode.style.top = null; - this.domNode.style.left = null; + this.domNode.style.top = ''; + this.domNode.style.left = ''; dispose(disposables); }; @@ -790,13 +791,13 @@ class TypeFilterController implements IDisposable { removeClass(this.domNode, positionClassName); addClass(this.domNode, 'dragging'); - disposables.push(toDisposable(() => removeClass(this.domNode, 'dragging'))); + disposables.add(toDisposable(() => removeClass(this.domNode, 'dragging'))); domEvent(document, 'dragover')(onDragOver, null, disposables); domEvent(this.domNode, 'dragend')(onDragEnd, null, disposables); StaticDND.CurrentDragAndDropData = new DragAndDropData('vscode-ui'); - disposables.push(toDisposable(() => StaticDND.CurrentDragAndDropData = undefined)); + disposables.add(toDisposable(() => StaticDND.CurrentDragAndDropData = undefined)); } private onDidSpliceModel(): void { @@ -855,9 +856,15 @@ class TypeFilterController implements IDisposable { } dispose() { - this.disable(); + if (this._enabled) { + this.domNode.remove(); + this.enabledDisposables.dispose(); + this._enabled = false; + this.triggered = false; + } + this._onDidChangePattern.dispose(); - this.disposables = dispose(this.disposables); + dispose(this.disposables); } } @@ -1175,7 +1182,7 @@ export abstract class AbstractTree implements IDisposable private typeFilterController?: TypeFilterController; private focusNavigationFilter: ((node: ITreeNode) => boolean) | undefined; private styleElement: HTMLStyleElement; - protected disposables: IDisposable[] = []; + protected readonly disposables = new DisposableStore(); get onDidScroll(): Event { return this.view.onDidScroll; } @@ -1224,16 +1231,17 @@ export abstract class AbstractTree implements IDisposable const onDidChangeCollapseStateRelay = new Relay>(); const onDidChangeActiveNodes = new Relay[]>(); const activeNodes = new EventCollection(onDidChangeActiveNodes.event); - this.renderers = renderers.map(r => new TreeRenderer(r, () => this.model, onDidChangeCollapseStateRelay.event, activeNodes, _options)); - this.disposables.push(...this.renderers); + for (let r of this.renderers) { + this.disposables.add(r); + } let filter: TypeFilter | undefined; if (_options.keyboardNavigationLabelProvider) { filter = new TypeFilter(this, _options.keyboardNavigationLabelProvider, _options.filter as any as ITreeFilter); _options = { ..._options, filter: filter as ITreeFilter }; // TODO need typescript help here - this.disposables.push(filter); + this.disposables.add(filter); } this.focus = new Trait(_options.identityProvider); @@ -1287,7 +1295,7 @@ export abstract class AbstractTree implements IDisposable const delegate = _options.keyboardNavigationDelegate || DefaultKeyboardNavigationDelegate; this.typeFilterController = new TypeFilterController(this, this.model, this.view, filter!, delegate); this.focusNavigationFilter = node => this.typeFilterController!.shouldAllowFocus(node); - this.disposables.push(this.typeFilterController!); + this.disposables.add(this.typeFilterController!); } this.styleElement = createStyleSheet(this.view.getHTMLElement()); @@ -1362,7 +1370,7 @@ export abstract class AbstractTree implements IDisposable } get scrollLeft(): number { - return this.view.scrollTop; + return this.view.scrollLeft; } set scrollLeft(scrollLeft: number) { @@ -1641,7 +1649,7 @@ export abstract class AbstractTree implements IDisposable } dispose(): void { - this.disposables = dispose(this.disposables); + dispose(this.disposables); this.view.dispose(); } } diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 4c29229a69..05ae77ac46 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -7,7 +7,7 @@ import { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate import { ObjectTree, IObjectTreeOptions, CompressibleObjectTree, ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list'; import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper } from 'vs/base/browser/ui/tree/tree'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { timeout, CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; @@ -88,7 +88,6 @@ class AsyncDataTreeRenderer implements IT readonly templateId: string; private renderedNodes = new Map, IDataTreeListTemplateData>(); - private disposables: IDisposable[] = []; constructor( protected renderer: ITreeRenderer, @@ -124,7 +123,6 @@ class AsyncDataTreeRenderer implements IT dispose(): void { this.renderedNodes.clear(); - this.disposables = dispose(this.disposables); } } @@ -292,7 +290,7 @@ export class AsyncDataTree implements IDisposable protected readonly nodeMapper: AsyncDataTreeNodeMapper = new WeakMapper(node => new AsyncDataTreeNodeWrapper(node)); - protected readonly disposables: IDisposable[] = []; + protected readonly disposables = new DisposableStore(); get onDidScroll(): Event { return this.tree.onDidScroll; } @@ -930,7 +928,7 @@ export class AsyncDataTree implements IDisposable } dispose(): void { - dispose(this.disposables); + this.disposables.dispose(); } } diff --git a/src/vs/base/browser/ui/tree/objectTree.ts b/src/vs/base/browser/ui/tree/objectTree.ts index d184822924..b5ed7a3b63 100644 --- a/src/vs/base/browser/ui/tree/objectTree.ts +++ b/src/vs/base/browser/ui/tree/objectTree.ts @@ -14,7 +14,7 @@ import { CompressibleObjectTreeModel, ElementMapper, ICompressedTreeNode, ICompr import { memoize } from 'vs/base/common/decorators'; export interface IObjectTreeOptions extends IAbstractTreeOptions { - sorter?: ITreeSorter; + readonly sorter?: ITreeSorter; } export class ObjectTree, TFilterData = void> extends AbstractTree { @@ -59,10 +59,6 @@ interface ICompressedTreeNodeProvider { getCompressedTreeNode(element: T): ITreeNode, TFilterData>; } -export interface ICompressibleObjectTreeOptions extends IObjectTreeOptions { - readonly elementMapper?: ElementMapper; -} - export interface ICompressibleTreeRenderer extends ITreeRenderer { renderCompressedElements(node: ITreeNode, TFilterData>, index: number, templateData: TTemplateData, height: number | undefined): void; disposeCompressedElements?(node: ITreeNode, TFilterData>, index: number, templateData: TTemplateData, height: number | undefined): void; @@ -136,6 +132,7 @@ export interface ICompressibleKeyboardNavigationLabelProvider extends IKeyboa } export interface ICompressibleObjectTreeOptions extends IObjectTreeOptions { + readonly elementMapper?: ElementMapper; readonly keyboardNavigationLabelProvider?: ICompressibleKeyboardNavigationLabelProvider; } @@ -164,7 +161,7 @@ function asObjectTreeOptions(compressedTreeNodeProvider: () => I export class CompressibleObjectTree, TFilterData = void> extends ObjectTree implements ICompressedTreeNodeProvider { - protected model: CompressibleObjectTreeModel; + protected model!: CompressibleObjectTreeModel; constructor( user: string, diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index ac03c7b9b7..14d6633ca9 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -29,7 +29,6 @@ export interface IAction extends IDisposable { class: string | undefined; enabled: boolean; checked: boolean; - radio: boolean; run(event?: any): Promise; } @@ -54,7 +53,6 @@ export interface IActionChangeEvent { readonly class?: string; readonly enabled?: boolean; readonly checked?: boolean; - readonly radio?: boolean; } export class Action extends Disposable implements IAction { @@ -68,7 +66,6 @@ export class Action extends Disposable implements IAction { protected _cssClass: string | undefined; protected _enabled: boolean = true; protected _checked: boolean = false; - protected _radio: boolean = false; protected readonly _actionCallback?: (event?: any) => Promise; constructor(id: string, label: string = '', cssClass: string = '', enabled: boolean = true, actionCallback?: (event?: any) => Promise) { @@ -152,14 +149,6 @@ export class Action extends Disposable implements IAction { this._setChecked(value); } - get radio(): boolean { - return this._radio; - } - - set radio(value: boolean) { - this._setRadio(value); - } - protected _setChecked(value: boolean): void { if (this._checked !== value) { this._checked = value; @@ -167,13 +156,6 @@ export class Action extends Disposable implements IAction { } } - protected _setRadio(value: boolean): void { - if (this._radio !== value) { - this._radio = value; - this._onDidChange.fire({ radio: value }); - } - } - run(event?: any, _data?: ITelemetryData): Promise { if (this._actionCallback) { return this._actionCallback(event); diff --git a/src/vs/base/common/cancellation.ts b/src/vs/base/common/cancellation.ts index 2563259cc4..5a38066025 100644 --- a/src/vs/base/common/cancellation.ts +++ b/src/vs/base/common/cancellation.ts @@ -116,7 +116,10 @@ export class CancellationTokenSource { } } - dispose(): void { + dispose(cancel: boolean = false): void { + if (cancel) { + this.cancel(); + } if (this._parentListener) { this._parentListener.dispose(); } diff --git a/src/vs/base/common/codicon.ts b/src/vs/base/common/codicon.ts new file mode 100644 index 0000000000..5fcb356aa4 --- /dev/null +++ b/src/vs/base/common/codicon.ts @@ -0,0 +1,135 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { matchesFuzzy, IMatch } from 'vs/base/common/filters'; +import { ltrim } from 'vs/base/common/strings'; + +const codiconStartMarker = '$('; + +export interface IParsedCodicons { + readonly text: string; + readonly codiconOffsets?: readonly number[]; +} + +export function parseCodicons(text: string): IParsedCodicons { + const firstCodiconIndex = text.indexOf(codiconStartMarker); + if (firstCodiconIndex === -1) { + return { text }; // return early if the word does not include an codicon + } + + return doParseCodicons(text, firstCodiconIndex); +} + +function doParseCodicons(text: string, firstCodiconIndex: number): IParsedCodicons { + const codiconOffsets: number[] = []; + let textWithoutCodicons: string = ''; + + function appendChars(chars: string) { + if (chars) { + textWithoutCodicons += chars; + + for (const _ of chars) { + codiconOffsets.push(codiconsOffset); // make sure to fill in codicon offsets + } + } + } + + let currentCodiconStart = -1; + let currentCodiconValue: string = ''; + let codiconsOffset = 0; + + let char: string; + let nextChar: string; + + let offset = firstCodiconIndex; + const length = text.length; + + // Append all characters until the first codicon + appendChars(text.substr(0, firstCodiconIndex)); + + // example: $(file-symlink-file) my cool $(other-codicon) entry + while (offset < length) { + char = text[offset]; + nextChar = text[offset + 1]; + + // beginning of codicon: some value $( <-- + if (char === codiconStartMarker[0] && nextChar === codiconStartMarker[1]) { + currentCodiconStart = offset; + + // if we had a previous potential codicon value without + // the closing ')', it was actually not an codicon and + // so we have to add it to the actual value + appendChars(currentCodiconValue); + + currentCodiconValue = codiconStartMarker; + + offset++; // jump over '(' + } + + // end of codicon: some value $(some-codicon) <-- + else if (char === ')' && currentCodiconStart !== -1) { + const currentCodiconLength = offset - currentCodiconStart + 1; // +1 to include the closing ')' + codiconsOffset += currentCodiconLength; + currentCodiconStart = -1; + currentCodiconValue = ''; + } + + // within codicon + else if (currentCodiconStart !== -1) { + // Make sure this is a real codicon name + if (/^[a-z0-9\-]$/i.test(char)) { + currentCodiconValue += char; + } else { + // This is not a real codicon, treat it as text + appendChars(currentCodiconValue); + + currentCodiconStart = -1; + currentCodiconValue = ''; + } + } + + // any value outside of codicons + else { + appendChars(char); + } + + offset++; + } + + // if we had a previous potential codicon value without + // the closing ')', it was actually not an codicon and + // so we have to add it to the actual value + appendChars(currentCodiconValue); + + return { text: textWithoutCodicons, codiconOffsets }; +} + +export function matchesFuzzyCodiconAware(query: string, target: IParsedCodicons, enableSeparateSubstringMatching = false): IMatch[] | null { + const { text, codiconOffsets } = target; + + // Return early if there are no codicon markers in the word to match against + if (!codiconOffsets || codiconOffsets.length === 0) { + return matchesFuzzy(query, text, enableSeparateSubstringMatching); + } + + // Trim the word to match against because it could have leading + // whitespace now if the word started with an codicon + const wordToMatchAgainstWithoutCodiconsTrimmed = ltrim(text, ' '); + const leadingWhitespaceOffset = text.length - wordToMatchAgainstWithoutCodiconsTrimmed.length; + + // match on value without codicons + const matches = matchesFuzzy(query, wordToMatchAgainstWithoutCodiconsTrimmed, enableSeparateSubstringMatching); + + // Map matches back to offsets with codicons and trimming + if (matches) { + for (const match of matches) { + const codiconOffset = codiconOffsets[match.start + leadingWhitespaceOffset] /* codicon offsets at index */ + leadingWhitespaceOffset /* overall leading whitespace offset */; + match.start += codiconOffset; + match.end += codiconOffset; + } + } + + return matches; +} diff --git a/src/vs/base/common/color.ts b/src/vs/base/common/color.ts index 0894cc929f..d5cf65fb2b 100644 --- a/src/vs/base/common/color.ts +++ b/src/vs/base/common/color.ts @@ -507,10 +507,6 @@ export namespace Color { * The default format will use HEX if opaque and RGBA otherwise. */ export function format(color: Color): string | null { - if (!color) { - return null; - } - if (color.isOpaque()) { return Color.Format.CSS.formatHex(color); } @@ -524,11 +520,6 @@ export namespace Color { * @param hex string (#RGB, #RGBA, #RRGGBB or #RRGGBBAA). */ export function parseHex(hex: string): Color | null { - if (!hex) { - // Invalid color - return null; - } - const length = hex.length; if (length === 0) { diff --git a/src/vs/base/common/diff/diff.ts b/src/vs/base/common/diff/diff.ts index d3a6073841..56b85a0d02 100644 --- a/src/vs/base/common/diff/diff.ts +++ b/src/vs/base/common/diff/diff.ts @@ -4,22 +4,29 @@ *--------------------------------------------------------------------------------------------*/ import { DiffChange } from 'vs/base/common/diff/diffChange'; +import { stringHash } from 'vs/base/common/hash'; +import { Constants } from 'vs/base/common/uint'; -function createStringSequence(a: string): ISequence { - return { - getLength() { return a.length; }, - getElementAtIndex(pos: number) { return a.charCodeAt(pos); } - }; +export class StringDiffSequence implements ISequence { + + constructor(private source: string) { } + + getElements(): Int32Array | number[] | string[] { + const source = this.source; + const characters = new Int32Array(source.length); + for (let i = 0, len = source.length; i < len; i++) { + characters[i] = source.charCodeAt(i); + } + return characters; + } } export function stringDiff(original: string, modified: string, pretty: boolean): IDiffChange[] { - return new LcsDiff(createStringSequence(original), createStringSequence(modified)).ComputeDiff(pretty); + return new LcsDiff(new StringDiffSequence(original), new StringDiffSequence(modified)).ComputeDiff(pretty).changes; } - export interface ISequence { - getLength(): number; - getElementAtIndex(index: number): number | string; + getElements(): Int32Array | number[] | string[]; } export interface IDiffChange { @@ -49,7 +56,12 @@ export interface IDiffChange { } export interface IContinueProcessingPredicate { - (furthestOriginalIndex: number, originalSequence: ISequence, matchLengthOfLongest: number): boolean; + (furthestOriginalIndex: number, matchLengthOfLongest: number): boolean; +} + +export interface IDiffResult { + quitEarly: boolean; + changes: IDiffChange[]; } // @@ -86,6 +98,11 @@ export class MyArray { destinationArray[destinationIndex + i] = sourceArray[sourceIndex + i]; } } + public static Copy2(sourceArray: Int32Array, sourceIndex: number, destinationArray: Int32Array, destinationIndex: number, length: number) { + for (let i = 0; i < length; i++) { + destinationArray[destinationIndex + i] = sourceArray[sourceIndex + i]; + } + } } //***************************************************************************** @@ -100,11 +117,9 @@ export class MyArray { // Our total memory usage for storing history is (worst-case): // 2 * [(MaxDifferencesHistory + 1) * (MaxDifferencesHistory + 1) - 1] * sizeof(int) // 2 * [1448*1448 - 1] * 4 = 16773624 = 16MB -let MaxDifferencesHistory = 1447; -//let MaxDifferencesHistory = 100; - - - +const enum LocalConstants { + MaxDifferencesHistory = 1447 +} /** * A utility class which helps to create the set of DiffChanges from @@ -127,8 +142,8 @@ class DiffChangeHelper { */ constructor() { this.m_changes = []; - this.m_originalStart = Number.MAX_VALUE; - this.m_modifiedStart = Number.MAX_VALUE; + this.m_originalStart = Constants.MAX_SAFE_SMALL_INTEGER; + this.m_modifiedStart = Constants.MAX_SAFE_SMALL_INTEGER; this.m_originalCount = 0; this.m_modifiedCount = 0; } @@ -147,8 +162,8 @@ class DiffChangeHelper { // Reset for the next change this.m_originalCount = 0; this.m_modifiedCount = 0; - this.m_originalStart = Number.MAX_VALUE; - this.m_modifiedStart = Number.MAX_VALUE; + this.m_originalStart = Constants.MAX_SAFE_SMALL_INTEGER; + this.m_modifiedStart = Constants.MAX_SAFE_SMALL_INTEGER; } /** @@ -214,39 +229,81 @@ class DiffChangeHelper { */ export class LcsDiff { - private OriginalSequence: ISequence; - private ModifiedSequence: ISequence; - private ContinueProcessingPredicate: IContinueProcessingPredicate | null; + private readonly ContinueProcessingPredicate: IContinueProcessingPredicate | null; - private m_forwardHistory: number[][]; - private m_reverseHistory: number[][]; + private readonly _hasStrings: boolean; + private readonly _originalStringElements: string[]; + private readonly _originalElementsOrHash: Int32Array; + private readonly _modifiedStringElements: string[]; + private readonly _modifiedElementsOrHash: Int32Array; + + private m_forwardHistory: Int32Array[]; + private m_reverseHistory: Int32Array[]; /** * Constructs the DiffFinder */ - constructor(originalSequence: ISequence, newSequence: ISequence, continueProcessingPredicate: IContinueProcessingPredicate | null = null) { - this.OriginalSequence = originalSequence; - this.ModifiedSequence = newSequence; + constructor(originalSequence: ISequence, modifiedSequence: ISequence, continueProcessingPredicate: IContinueProcessingPredicate | null = null) { this.ContinueProcessingPredicate = continueProcessingPredicate; + const [originalStringElements, originalElementsOrHash, originalHasStrings] = LcsDiff._getElements(originalSequence); + const [modifiedStringElements, modifiedElementsOrHash, modifiedHasStrings] = LcsDiff._getElements(modifiedSequence); + + this._hasStrings = (originalHasStrings && modifiedHasStrings); + this._originalStringElements = originalStringElements; + this._originalElementsOrHash = originalElementsOrHash; + this._modifiedStringElements = modifiedStringElements; + this._modifiedElementsOrHash = modifiedElementsOrHash; + this.m_forwardHistory = []; this.m_reverseHistory = []; } + private static _isStringArray(arr: Int32Array | number[] | string[]): arr is string[] { + return (arr.length > 0 && typeof arr[0] === 'string'); + } + + private static _getElements(sequence: ISequence): [string[], Int32Array, boolean] { + const elements = sequence.getElements(); + + if (LcsDiff._isStringArray(elements)) { + const hashes = new Int32Array(elements.length); + for (let i = 0, len = elements.length; i < len; i++) { + hashes[i] = stringHash(elements[i], 0); + } + return [elements, hashes, true]; + } + + if (elements instanceof Int32Array) { + return [[], elements, false]; + } + + return [[], new Int32Array(elements), false]; + } + private ElementsAreEqual(originalIndex: number, newIndex: number): boolean { - return (this.OriginalSequence.getElementAtIndex(originalIndex) === this.ModifiedSequence.getElementAtIndex(newIndex)); + if (this._originalElementsOrHash[originalIndex] !== this._modifiedElementsOrHash[newIndex]) { + return false; + } + return (this._hasStrings ? this._originalStringElements[originalIndex] === this._modifiedStringElements[newIndex] : true); } private OriginalElementsAreEqual(index1: number, index2: number): boolean { - return (this.OriginalSequence.getElementAtIndex(index1) === this.OriginalSequence.getElementAtIndex(index2)); + if (this._originalElementsOrHash[index1] !== this._originalElementsOrHash[index2]) { + return false; + } + return (this._hasStrings ? this._originalStringElements[index1] === this._originalStringElements[index2] : true); } private ModifiedElementsAreEqual(index1: number, index2: number): boolean { - return (this.ModifiedSequence.getElementAtIndex(index1) === this.ModifiedSequence.getElementAtIndex(index2)); + if (this._modifiedElementsOrHash[index1] !== this._modifiedElementsOrHash[index2]) { + return false; + } + return (this._hasStrings ? this._modifiedStringElements[index1] === this._modifiedStringElements[index2] : true); } - public ComputeDiff(pretty: boolean): IDiffChange[] { - return this._ComputeDiff(0, this.OriginalSequence.getLength() - 1, 0, this.ModifiedSequence.getLength() - 1, pretty); + public ComputeDiff(pretty: boolean): IDiffResult { + return this._ComputeDiff(0, this._originalElementsOrHash.length - 1, 0, this._modifiedElementsOrHash.length - 1, pretty); } /** @@ -254,18 +311,21 @@ export class LcsDiff { * sequences on the bounded range. * @returns An array of the differences between the two input sequences. */ - private _ComputeDiff(originalStart: number, originalEnd: number, modifiedStart: number, modifiedEnd: number, pretty: boolean): DiffChange[] { - let quitEarlyArr = [false]; + private _ComputeDiff(originalStart: number, originalEnd: number, modifiedStart: number, modifiedEnd: number, pretty: boolean): IDiffResult { + const quitEarlyArr = [false]; let changes = this.ComputeDiffRecursive(originalStart, originalEnd, modifiedStart, modifiedEnd, quitEarlyArr); if (pretty) { // We have to clean up the computed diff to be more intuitive // but it turns out this cannot be done correctly until the entire set // of diffs have been computed - return this.PrettifyChanges(changes); + changes = this.PrettifyChanges(changes); } - return changes; + return { + quitEarly: quitEarlyArr[0], + changes: changes + }; } /** @@ -318,11 +378,12 @@ export class LcsDiff { } // This problem can be solved using the Divide-And-Conquer technique. - let midOriginalArr = [0], midModifiedArr = [0]; - let result = this.ComputeRecursionPoint(originalStart, originalEnd, modifiedStart, modifiedEnd, midOriginalArr, midModifiedArr, quitEarlyArr); + const midOriginalArr = [0]; + const midModifiedArr = [0]; + const result = this.ComputeRecursionPoint(originalStart, originalEnd, modifiedStart, modifiedEnd, midOriginalArr, midModifiedArr, quitEarlyArr); - let midOriginal = midOriginalArr[0]; - let midModified = midModifiedArr[0]; + const midOriginal = midOriginalArr[0]; + const midModified = midModifiedArr[0]; if (result !== null) { // Result is not-null when there was enough memory to compute the changes while @@ -334,7 +395,7 @@ export class LcsDiff { // Second Half: (midOriginal + 1, minModified + 1) to (originalEnd, modifiedEnd) // NOTE: ComputeDiff() is inclusive, therefore the second range starts on the next point - let leftChanges = this.ComputeDiffRecursive(originalStart, midOriginal, modifiedStart, midModified, quitEarlyArr); + const leftChanges = this.ComputeDiffRecursive(originalStart, midOriginal, modifiedStart, midModified, quitEarlyArr); let rightChanges: DiffChange[] = []; if (!quitEarlyArr[0]) { @@ -358,24 +419,25 @@ export class LcsDiff { private WALKTRACE(diagonalForwardBase: number, diagonalForwardStart: number, diagonalForwardEnd: number, diagonalForwardOffset: number, diagonalReverseBase: number, diagonalReverseStart: number, diagonalReverseEnd: number, diagonalReverseOffset: number, - forwardPoints: number[], reversePoints: number[], + forwardPoints: Int32Array, reversePoints: Int32Array, originalIndex: number, originalEnd: number, midOriginalArr: number[], modifiedIndex: number, modifiedEnd: number, midModifiedArr: number[], - deltaIsEven: boolean, quitEarlyArr: boolean[]): DiffChange[] { - let forwardChanges: DiffChange[] | null = null, reverseChanges: DiffChange[] | null = null; + deltaIsEven: boolean, quitEarlyArr: boolean[] + ): DiffChange[] { + let forwardChanges: DiffChange[] | null = null; + let reverseChanges: DiffChange[] | null = null; // First, walk backward through the forward diagonals history let changeHelper = new DiffChangeHelper(); let diagonalMin = diagonalForwardStart; let diagonalMax = diagonalForwardEnd; let diagonalRelative = (midOriginalArr[0] - midModifiedArr[0]) - diagonalForwardOffset; - let lastOriginalIndex = Number.MIN_VALUE; + let lastOriginalIndex = Constants.MIN_SAFE_SMALL_INTEGER; let historyIndex = this.m_forwardHistory.length - 1; - let diagonal: number; do { // Get the diagonal index from the relative diagonal number - diagonal = diagonalRelative + diagonalForwardBase; + const diagonal = diagonalRelative + diagonalForwardBase; // Figure out where we came from if (diagonal === diagonalMin || (diagonal < diagonalMax && forwardPoints[diagonal - 1] < forwardPoints[diagonal + 1])) { @@ -420,7 +482,7 @@ export class LcsDiff { let modifiedStartPoint = midModifiedArr[0] + 1; if (forwardChanges !== null && forwardChanges.length > 0) { - let lastForwardChange = forwardChanges[forwardChanges.length - 1]; + const lastForwardChange = forwardChanges[forwardChanges.length - 1]; originalStartPoint = Math.max(originalStartPoint, lastForwardChange.getOriginalEnd()); modifiedStartPoint = Math.max(modifiedStartPoint, lastForwardChange.getModifiedEnd()); } @@ -435,12 +497,12 @@ export class LcsDiff { diagonalMin = diagonalReverseStart; diagonalMax = diagonalReverseEnd; diagonalRelative = (midOriginalArr[0] - midModifiedArr[0]) - diagonalReverseOffset; - lastOriginalIndex = Number.MAX_VALUE; + lastOriginalIndex = Constants.MAX_SAFE_SMALL_INTEGER; historyIndex = (deltaIsEven) ? this.m_reverseHistory.length - 1 : this.m_reverseHistory.length - 2; do { // Get the diagonal index from the relative diagonal number - diagonal = diagonalRelative + diagonalReverseBase; + const diagonal = diagonalRelative + diagonalReverseBase; // Figure out where we came from if (diagonal === diagonalMin || (diagonal < diagonalMax && reversePoints[diagonal - 1] >= reversePoints[diagonal + 1])) { @@ -501,7 +563,6 @@ export class LcsDiff { let originalIndex = 0, modifiedIndex = 0; let diagonalForwardStart = 0, diagonalForwardEnd = 0; let diagonalReverseStart = 0, diagonalReverseEnd = 0; - let numDifferences: number; // To traverse the edit graph and produce the proper LCS, our actual // start position is just outside the given boundary @@ -521,26 +582,26 @@ export class LcsDiff { // The integer value in the cell represents the originalIndex of the furthest // reaching point found so far that ends in that diagonal. // The modifiedIndex can be computed mathematically from the originalIndex and the diagonal number. - let maxDifferences = (originalEnd - originalStart) + (modifiedEnd - modifiedStart); - let numDiagonals = maxDifferences + 1; - let forwardPoints: number[] = new Array(numDiagonals); - let reversePoints: number[] = new Array(numDiagonals); + const maxDifferences = (originalEnd - originalStart) + (modifiedEnd - modifiedStart); + const numDiagonals = maxDifferences + 1; + const forwardPoints = new Int32Array(numDiagonals); + const reversePoints = new Int32Array(numDiagonals); // diagonalForwardBase: Index into forwardPoints of the diagonal which passes through (originalStart, modifiedStart) // diagonalReverseBase: Index into reversePoints of the diagonal which passes through (originalEnd, modifiedEnd) - let diagonalForwardBase = (modifiedEnd - modifiedStart); - let diagonalReverseBase = (originalEnd - originalStart); + const diagonalForwardBase = (modifiedEnd - modifiedStart); + const diagonalReverseBase = (originalEnd - originalStart); // diagonalForwardOffset: Geometric offset which allows modifiedIndex to be computed from originalIndex and the // diagonal number (relative to diagonalForwardBase) // diagonalReverseOffset: Geometric offset which allows modifiedIndex to be computed from originalIndex and the // diagonal number (relative to diagonalReverseBase) - let diagonalForwardOffset = (originalStart - modifiedStart); - let diagonalReverseOffset = (originalEnd - modifiedEnd); + const diagonalForwardOffset = (originalStart - modifiedStart); + const diagonalReverseOffset = (originalEnd - modifiedEnd); // delta: The difference between the end diagonal and the start diagonal. This is used to relate diagonal numbers // relative to the start diagonal with diagonal numbers relative to the end diagonal. // The Even/Oddn-ness of this delta is important for determining when we should check for overlap - let delta = diagonalReverseBase - diagonalForwardBase; - let deltaIsEven = (delta % 2 === 0); + const delta = diagonalReverseBase - diagonalForwardBase; + const deltaIsEven = (delta % 2 === 0); // Here we set up the start and end points as the furthest points found so far // in both the forward and reverse directions, respectively @@ -559,15 +620,14 @@ export class LcsDiff { // away from the reference diagonal (which is diagonalForwardBase for forward, diagonalReverseBase for reverse). // --We extend on even diagonals (relative to the reference diagonal) only when numDifferences // is even and odd diagonals only when numDifferences is odd. - let diagonal: number, tempOriginalIndex: number; - for (numDifferences = 1; numDifferences <= (maxDifferences / 2) + 1; numDifferences++) { + for (let numDifferences = 1; numDifferences <= (maxDifferences / 2) + 1; numDifferences++) { let furthestOriginalIndex = 0; let furthestModifiedIndex = 0; // Run the algorithm in the forward direction diagonalForwardStart = this.ClipDiagonalBound(diagonalForwardBase - numDifferences, numDifferences, diagonalForwardBase, numDiagonals); diagonalForwardEnd = this.ClipDiagonalBound(diagonalForwardBase + numDifferences, numDifferences, diagonalForwardBase, numDiagonals); - for (diagonal = diagonalForwardStart; diagonal <= diagonalForwardEnd; diagonal += 2) { + for (let diagonal = diagonalForwardStart; diagonal <= diagonalForwardEnd; diagonal += 2) { // STEP 1: We extend the furthest reaching point in the present diagonal // by looking at the diagonals above and below and picking the one whose point // is further away from the start point (originalStart, modifiedStart) @@ -579,7 +639,7 @@ export class LcsDiff { modifiedIndex = originalIndex - (diagonal - diagonalForwardBase) - diagonalForwardOffset; // Save the current originalIndex so we can test for false overlap in step 3 - tempOriginalIndex = originalIndex; + const tempOriginalIndex = originalIndex; // STEP 2: We can continue to extend the furthest reaching point in the present diagonal // so long as the elements are equal. @@ -603,7 +663,7 @@ export class LcsDiff { midOriginalArr[0] = originalIndex; midModifiedArr[0] = modifiedIndex; - if (tempOriginalIndex <= reversePoints[diagonal] && MaxDifferencesHistory > 0 && numDifferences <= (MaxDifferencesHistory + 1)) { + if (tempOriginalIndex <= reversePoints[diagonal] && LocalConstants.MaxDifferencesHistory > 0 && numDifferences <= (LocalConstants.MaxDifferencesHistory + 1)) { // BINGO! We overlapped, and we have the full trace in memory! return this.WALKTRACE(diagonalForwardBase, diagonalForwardStart, diagonalForwardEnd, diagonalForwardOffset, diagonalReverseBase, diagonalReverseStart, diagonalReverseEnd, diagonalReverseOffset, @@ -622,9 +682,9 @@ export class LcsDiff { } // Check to see if we should be quitting early, before moving on to the next iteration. - let matchLengthOfLongest = ((furthestOriginalIndex - originalStart) + (furthestModifiedIndex - modifiedStart) - numDifferences) / 2; + const matchLengthOfLongest = ((furthestOriginalIndex - originalStart) + (furthestModifiedIndex - modifiedStart) - numDifferences) / 2; - if (this.ContinueProcessingPredicate !== null && !this.ContinueProcessingPredicate(furthestOriginalIndex, this.OriginalSequence, matchLengthOfLongest)) { + if (this.ContinueProcessingPredicate !== null && !this.ContinueProcessingPredicate(furthestOriginalIndex, matchLengthOfLongest)) { // We can't finish, so skip ahead to generating a result from what we have. quitEarlyArr[0] = true; @@ -632,7 +692,7 @@ export class LcsDiff { midOriginalArr[0] = furthestOriginalIndex; midModifiedArr[0] = furthestModifiedIndex; - if (matchLengthOfLongest > 0 && MaxDifferencesHistory > 0 && numDifferences <= (MaxDifferencesHistory + 1)) { + if (matchLengthOfLongest > 0 && LocalConstants.MaxDifferencesHistory > 0 && numDifferences <= (LocalConstants.MaxDifferencesHistory + 1)) { // Enough of the history is in memory to walk it backwards return this.WALKTRACE(diagonalForwardBase, diagonalForwardStart, diagonalForwardEnd, diagonalForwardOffset, diagonalReverseBase, diagonalReverseStart, diagonalReverseEnd, diagonalReverseOffset, @@ -659,7 +719,7 @@ export class LcsDiff { // Run the algorithm in the reverse direction diagonalReverseStart = this.ClipDiagonalBound(diagonalReverseBase - numDifferences, numDifferences, diagonalReverseBase, numDiagonals); diagonalReverseEnd = this.ClipDiagonalBound(diagonalReverseBase + numDifferences, numDifferences, diagonalReverseBase, numDiagonals); - for (diagonal = diagonalReverseStart; diagonal <= diagonalReverseEnd; diagonal += 2) { + for (let diagonal = diagonalReverseStart; diagonal <= diagonalReverseEnd; diagonal += 2) { // STEP 1: We extend the furthest reaching point in the present diagonal // by looking at the diagonals above and below and picking the one whose point // is further away from the start point (originalEnd, modifiedEnd) @@ -671,7 +731,7 @@ export class LcsDiff { modifiedIndex = originalIndex - (diagonal - diagonalReverseBase) - diagonalReverseOffset; // Save the current originalIndex so we can test for false overlap - tempOriginalIndex = originalIndex; + const tempOriginalIndex = originalIndex; // STEP 2: We can continue to extend the furthest reaching point in the present diagonal // as long as the elements are equal. @@ -689,7 +749,7 @@ export class LcsDiff { midOriginalArr[0] = originalIndex; midModifiedArr[0] = modifiedIndex; - if (tempOriginalIndex >= forwardPoints[diagonal] && MaxDifferencesHistory > 0 && numDifferences <= (MaxDifferencesHistory + 1)) { + if (tempOriginalIndex >= forwardPoints[diagonal] && LocalConstants.MaxDifferencesHistory > 0 && numDifferences <= (LocalConstants.MaxDifferencesHistory + 1)) { // BINGO! We overlapped, and we have the full trace in memory! return this.WALKTRACE(diagonalForwardBase, diagonalForwardStart, diagonalForwardEnd, diagonalForwardOffset, diagonalReverseBase, diagonalReverseStart, diagonalReverseEnd, diagonalReverseOffset, @@ -708,24 +768,22 @@ export class LcsDiff { } // Save current vectors to history before the next iteration - if (numDifferences <= MaxDifferencesHistory) { + if (numDifferences <= LocalConstants.MaxDifferencesHistory) { // We are allocating space for one extra int, which we fill with // the index of the diagonal base index - let temp: number[] = new Array(diagonalForwardEnd - diagonalForwardStart + 2); + let temp = new Int32Array(diagonalForwardEnd - diagonalForwardStart + 2); temp[0] = diagonalForwardBase - diagonalForwardStart + 1; - MyArray.Copy(forwardPoints, diagonalForwardStart, temp, 1, diagonalForwardEnd - diagonalForwardStart + 1); + MyArray.Copy2(forwardPoints, diagonalForwardStart, temp, 1, diagonalForwardEnd - diagonalForwardStart + 1); this.m_forwardHistory.push(temp); - temp = new Array(diagonalReverseEnd - diagonalReverseStart + 2); + temp = new Int32Array(diagonalReverseEnd - diagonalReverseStart + 2); temp[0] = diagonalReverseBase - diagonalReverseStart + 1; - MyArray.Copy(reversePoints, diagonalReverseStart, temp, 1, diagonalReverseEnd - diagonalReverseStart + 1); + MyArray.Copy2(reversePoints, diagonalReverseStart, temp, 1, diagonalReverseEnd - diagonalReverseStart + 1); this.m_reverseHistory.push(temp); } } - - // If we got here, then we have the full trace in history. We just have to convert it to a change list // NOTE: This part is a bit messy return this.WALKTRACE(diagonalForwardBase, diagonalForwardStart, diagonalForwardEnd, diagonalForwardOffset, @@ -750,8 +808,8 @@ export class LcsDiff { // Shift all the changes down first for (let i = 0; i < changes.length; i++) { const change = changes[i]; - const originalStop = (i < changes.length - 1) ? changes[i + 1].originalStart : this.OriginalSequence.getLength(); - const modifiedStop = (i < changes.length - 1) ? changes[i + 1].modifiedStart : this.ModifiedSequence.getLength(); + const originalStop = (i < changes.length - 1) ? changes[i + 1].originalStart : this._originalElementsOrHash.length; + const modifiedStop = (i < changes.length - 1) ? changes[i + 1].modifiedStart : this._modifiedElementsOrHash.length; const checkOriginal = change.originalLength > 0; const checkModified = change.modifiedLength > 0; @@ -795,8 +853,8 @@ export class LcsDiff { let bestScore = this._boundaryScore(change.originalStart, change.originalLength, change.modifiedStart, change.modifiedLength); for (let delta = 1; ; delta++) { - let originalStart = change.originalStart - delta; - let modifiedStart = change.modifiedStart - delta; + const originalStart = change.originalStart - delta; + const modifiedStart = change.modifiedStart - delta; if (originalStart < originalStop || modifiedStart < modifiedStop) { break; @@ -810,7 +868,7 @@ export class LcsDiff { break; } - let score = this._boundaryScore(originalStart, change.originalLength, modifiedStart, change.modifiedLength); + const score = this._boundaryScore(originalStart, change.originalLength, modifiedStart, change.modifiedLength); if (score > bestScore) { bestScore = score; @@ -826,11 +884,10 @@ export class LcsDiff { } private _OriginalIsBoundary(index: number): boolean { - if (index <= 0 || index >= this.OriginalSequence.getLength() - 1) { + if (index <= 0 || index >= this._originalElementsOrHash.length - 1) { return true; } - const element = this.OriginalSequence.getElementAtIndex(index); - return (typeof element === 'string' && /^\s*$/.test(element)); + return (this._hasStrings && /^\s*$/.test(this._originalStringElements[index])); } private _OriginalRegionIsBoundary(originalStart: number, originalLength: number): boolean { @@ -838,7 +895,7 @@ export class LcsDiff { return true; } if (originalLength > 0) { - let originalEnd = originalStart + originalLength; + const originalEnd = originalStart + originalLength; if (this._OriginalIsBoundary(originalEnd - 1) || this._OriginalIsBoundary(originalEnd)) { return true; } @@ -847,11 +904,10 @@ export class LcsDiff { } private _ModifiedIsBoundary(index: number): boolean { - if (index <= 0 || index >= this.ModifiedSequence.getLength() - 1) { + if (index <= 0 || index >= this._modifiedElementsOrHash.length - 1) { return true; } - const element = this.ModifiedSequence.getElementAtIndex(index); - return (typeof element === 'string' && /^\s*$/.test(element)); + return (this._hasStrings && /^\s*$/.test(this._modifiedStringElements[index])); } private _ModifiedRegionIsBoundary(modifiedStart: number, modifiedLength: number): boolean { @@ -859,7 +915,7 @@ export class LcsDiff { return true; } if (modifiedLength > 0) { - let modifiedEnd = modifiedStart + modifiedLength; + const modifiedEnd = modifiedStart + modifiedLength; if (this._ModifiedIsBoundary(modifiedEnd - 1) || this._ModifiedIsBoundary(modifiedEnd)) { return true; } @@ -868,8 +924,8 @@ export class LcsDiff { } private _boundaryScore(originalStart: number, originalLength: number, modifiedStart: number, modifiedLength: number): number { - let originalScore = (this._OriginalRegionIsBoundary(originalStart, originalLength) ? 1 : 0); - let modifiedScore = (this._ModifiedRegionIsBoundary(modifiedStart, modifiedLength) ? 1 : 0); + const originalScore = (this._OriginalRegionIsBoundary(originalStart, originalLength) ? 1 : 0); + const modifiedScore = (this._ModifiedRegionIsBoundary(modifiedStart, modifiedLength) ? 1 : 0); return (originalScore + modifiedScore); } @@ -890,14 +946,14 @@ export class LcsDiff { // might recurse in the middle of a change thereby splitting it into // two changes. Here in the combining stage, we detect and fuse those // changes back together - let result = new Array(left.length + right.length - 1); + const result = new Array(left.length + right.length - 1); MyArray.Copy(left, 0, result, 0, left.length - 1); result[left.length - 1] = mergedChangeArr[0]; MyArray.Copy(right, 1, result, left.length, right.length - 1); return result; } else { - let result = new Array(left.length + right.length); + const result = new Array(left.length + right.length); MyArray.Copy(left, 0, result, 0, left.length); MyArray.Copy(right, 0, result, left.length, right.length); @@ -918,9 +974,9 @@ export class LcsDiff { Debug.Assert(left.modifiedStart <= right.modifiedStart, 'Left change is not less than or equal to right change'); if (left.originalStart + left.originalLength >= right.originalStart || left.modifiedStart + left.modifiedLength >= right.modifiedStart) { - let originalStart = left.originalStart; + const originalStart = left.originalStart; let originalLength = left.originalLength; - let modifiedStart = left.modifiedStart; + const modifiedStart = left.modifiedStart; let modifiedLength = left.modifiedLength; if (left.originalStart + left.originalLength >= right.originalStart) { @@ -958,15 +1014,15 @@ export class LcsDiff { // diagonalsBelow: The number of diagonals below the reference diagonal // diagonalsAbove: The number of diagonals above the reference diagonal - let diagonalsBelow = diagonalBaseIndex; - let diagonalsAbove = numDiagonals - diagonalBaseIndex - 1; - let diffEven = (numDifferences % 2 === 0); + const diagonalsBelow = diagonalBaseIndex; + const diagonalsAbove = numDiagonals - diagonalBaseIndex - 1; + const diffEven = (numDifferences % 2 === 0); if (diagonal < 0) { - let lowerBoundEven = (diagonalsBelow % 2 === 0); + const lowerBoundEven = (diagonalsBelow % 2 === 0); return (diffEven === lowerBoundEven) ? 0 : 1; } else { - let upperBoundEven = (diagonalsAbove % 2 === 0); + const upperBoundEven = (diagonalsAbove % 2 === 0); return (diffEven === upperBoundEven) ? numDiagonals - 1 : numDiagonals - 2; } } diff --git a/src/vs/base/common/hash.ts b/src/vs/base/common/hash.ts index 7c418a752d..72ca23bf07 100644 --- a/src/vs/base/common/hash.ts +++ b/src/vs/base/common/hash.ts @@ -36,7 +36,7 @@ function booleanHash(b: boolean, initialHashVal: number): number { return numberHash(b ? 433 : 863, initialHashVal); } -function stringHash(s: string, hashVal: number) { +export function stringHash(s: string, hashVal: number) { hashVal = numberHash(149417, hashVal); for (let i = 0, length = s.length; i < length; i++) { hashVal = numberHash(s.charCodeAt(i), hashVal); diff --git a/src/vs/base/common/htmlContent.ts b/src/vs/base/common/htmlContent.ts index 9edbd10cc9..655ae4a6f8 100644 --- a/src/vs/base/common/htmlContent.ts +++ b/src/vs/base/common/htmlContent.ts @@ -7,23 +7,27 @@ import { equals } from 'vs/base/common/arrays'; import { UriComponents } from 'vs/base/common/uri'; export interface IMarkdownString { - value: string; - isTrusted?: boolean; + readonly value: string; + readonly isTrusted?: boolean; uris?: { [href: string]: UriComponents }; } export class MarkdownString implements IMarkdownString { - value: string; - isTrusted?: boolean; + private _value: string; + private _isTrusted: boolean; - constructor(value: string = '') { - this.value = value; + constructor(value: string = '', isTrusted = false) { + this._value = value; + this._isTrusted = isTrusted; } + get value() { return this._value; } + get isTrusted() { return this._isTrusted; } + appendText(value: string): MarkdownString { // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash - this.value += value + this._value += value .replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&') .replace('\n', '\n\n'); @@ -31,16 +35,16 @@ export class MarkdownString implements IMarkdownString { } appendMarkdown(value: string): MarkdownString { - this.value += value; + this._value += value; return this; } appendCodeblock(langId: string, code: string): MarkdownString { - this.value += '\n```'; - this.value += langId; - this.value += '\n'; - this.value += code; - this.value += '\n```\n'; + this._value += '\n```'; + this._value += langId; + this._value += '\n'; + this._value += code; + this._value += '\n```\n'; return this; } } diff --git a/src/vs/base/common/iterator.ts b/src/vs/base/common/iterator.ts index 2e3687f583..f9bee211a7 100644 --- a/src/vs/base/common/iterator.ts +++ b/src/vs/base/common/iterator.ts @@ -60,7 +60,7 @@ export module Iterator { }; } - export function fromArray(array: T[], index = 0, length = array.length): Iterator { + export function fromArray(array: ReadonlyArray, index = 0, length = array.length): Iterator { return { next(): IteratorResult { if (index >= length) { diff --git a/src/vs/base/common/labels.ts b/src/vs/base/common/labels.ts index 9873c8d76d..7f8b6ad2be 100644 --- a/src/vs/base/common/labels.ts +++ b/src/vs/base/common/labels.ts @@ -4,12 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { sep, posix, normalize } from 'vs/base/common/path'; +import { sep, posix, normalize, win32 } from 'vs/base/common/path'; import { endsWith, startsWithIgnoreCase, rtrim, startsWith } from 'vs/base/common/strings'; import { Schemas } from 'vs/base/common/network'; import { isLinux, isWindows, isMacintosh } from 'vs/base/common/platform'; import { isEqual, basename, relativePath } from 'vs/base/common/resources'; -import { CharCode } from 'vs/base/common/charCode'; export interface IWorkspaceFolderProvider { getWorkspaceFolder(resource: URI): { uri: URI, name?: string } | null; @@ -44,7 +43,7 @@ export function getPathLabel(resource: URI | string, userHomeProvider?: IUserHom } if (hasMultipleRoots) { - const rootName = (baseResource && baseResource.name) ? baseResource.name : basename(baseResource.uri); + const rootName = baseResource.name ? baseResource.name : basename(baseResource.uri); pathLabel = pathLabel ? (rootName + ' • ' + pathLabel) : rootName; // always show root basename if there are multiple } @@ -388,11 +387,12 @@ export function unmnemonicLabel(label: string): string { * Splits a path in name and parent path, supporting both '/' and '\' */ export function splitName(fullPath: string): { name: string, parentPath: string } { - for (let i = fullPath.length - 1; i >= 1; i--) { - const code = fullPath.charCodeAt(i); - if (code === CharCode.Slash || code === CharCode.Backslash) { - return { parentPath: fullPath.substr(0, i), name: fullPath.substr(i + 1) }; - } + const p = fullPath.indexOf('/') !== -1 ? posix : win32; + const name = p.basename(fullPath); + const parentPath = p.dirname(fullPath); + if (name.length) { + return { name, parentPath }; } - return { parentPath: '', name: fullPath }; + // only the root segment + return { name: parentPath, parentPath: '' }; } diff --git a/src/vs/base/common/lazy.ts b/src/vs/base/common/lazy.ts new file mode 100644 index 0000000000..ad3d0a6c6c --- /dev/null +++ b/src/vs/base/common/lazy.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * A value that is resolved synchronously when it is first needed. + */ +export interface Lazy { + + hasValue(): boolean; + + + getValue(): T; + + + map(f: (x: T) => R): Lazy; +} + +export class Lazy { + + private _didRun: boolean = false; + private _value?: T; + private _error: any; + + constructor( + private readonly executor: () => T, + ) { } + + /** + * True if the lazy value has been resolved. + */ + hasValue() { return this._didRun; } + + /** + * Get the wrapped value. + * + * This will force evaluation of the lazy value if it has not been resolved yet. Lazy values are only + * resolved once. `getValue` will re-throw exceptions that are hit while resolving the value + */ + getValue(): T { + if (!this._didRun) { + try { + this._value = this.executor(); + } catch (err) { + this._error = err; + } finally { + this._didRun = true; + } + } + if (this._error) { + throw this._error; + } + return this._value!; + } + + /** + * Create a new lazy value that is the result of applying `f` to the wrapped value. + * + * This does not force the evaluation of the current lazy value. + */ + map(f: (x: T) => R): Lazy { + return new Lazy(() => f(this.getValue())); + } +} diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index 2743856b73..e172e766e9 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -138,7 +138,7 @@ export class DisposableStore implements IDisposable { export abstract class Disposable implements IDisposable { - static None = Object.freeze({ dispose() { } }); + static readonly None = Object.freeze({ dispose() { } }); private readonly _store = new DisposableStore(); diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index a8c49969ec..252e65c0b9 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -117,6 +117,8 @@ export class PathIterator implements IKeyIterator { private _from!: number; private _to!: number; + constructor(private _splitOnBackslash: boolean = true) { } + reset(key: string): this { this._value = key.replace(/\\$|\/$/, ''); this._from = 0; @@ -134,7 +136,7 @@ export class PathIterator implements IKeyIterator { let justSeps = true; for (; this._to < this._value.length; this._to++) { const ch = this._value.charCodeAt(this._to); - if (ch === CharCode.Slash || ch === CharCode.Backslash) { + if (ch === CharCode.Slash || this._splitOnBackslash && ch === CharCode.Backslash) { if (justSeps) { this._from++; } else { diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 3dfa852a4f..c4b3cdd9ec 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -56,53 +56,49 @@ export namespace Schemas { } class RemoteAuthoritiesImpl { - private readonly _hosts: { [authority: string]: string; }; - private readonly _ports: { [authority: string]: number; }; - private readonly _connectionTokens: { [authority: string]: string; }; - private _preferredWebSchema: 'http' | 'https'; - private _delegate: ((uri: URI) => URI) | null; + private readonly _hosts: { [authority: string]: string | undefined; } = Object.create(null); + private readonly _ports: { [authority: string]: number | undefined; } = Object.create(null); + private readonly _connectionTokens: { [authority: string]: string | undefined; } = Object.create(null); + private _preferredWebSchema: 'http' | 'https' = 'http'; + private _delegate: ((uri: URI) => URI) | null = null; - constructor() { - this._hosts = Object.create(null); - this._ports = Object.create(null); - this._connectionTokens = Object.create(null); - this._preferredWebSchema = 'http'; - this._delegate = null; - } - - public setPreferredWebSchema(schema: 'http' | 'https') { + setPreferredWebSchema(schema: 'http' | 'https') { this._preferredWebSchema = schema; } - public setDelegate(delegate: (uri: URI) => URI): void { + setDelegate(delegate: (uri: URI) => URI): void { this._delegate = delegate; } - public set(authority: string, host: string, port: number): void { + set(authority: string, host: string, port: number): void { this._hosts[authority] = host; this._ports[authority] = port; } - public setConnectionToken(authority: string, connectionToken: string): void { + setConnectionToken(authority: string, connectionToken: string): void { this._connectionTokens[authority] = connectionToken; } - public rewrite(uri: URI): URI { + rewrite(uri: URI): URI { if (this._delegate) { return this._delegate(uri); } const authority = uri.authority; let host = this._hosts[authority]; - if (host.indexOf(':') !== -1) { + if (host && host.indexOf(':') !== -1) { host = `[${host}]`; } const port = this._ports[authority]; const connectionToken = this._connectionTokens[authority]; + let query = `path=${encodeURIComponent(uri.path)}`; + if (typeof connectionToken === 'string') { + query += `&tkn=${encodeURIComponent(connectionToken)}`; + } return URI.from({ scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource, authority: `${host}:${port}`, path: `/vscode-remote-resource`, - query: `path=${encodeURIComponent(uri.path)}&tkn=${encodeURIComponent(connectionToken)}` + query }); } } diff --git a/src/vs/base/common/octicon.ts b/src/vs/base/common/octicon.ts deleted file mode 100644 index e74104c2f9..0000000000 --- a/src/vs/base/common/octicon.ts +++ /dev/null @@ -1,126 +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 { matchesFuzzy, IMatch } from 'vs/base/common/filters'; -import { ltrim } from 'vs/base/common/strings'; - -const octiconStartMarker = '$('; - -export interface IParsedOcticons { - text: string; - octiconOffsets?: number[]; -} - -export function parseOcticons(text: string): IParsedOcticons { - const firstOcticonIndex = text.indexOf(octiconStartMarker); - if (firstOcticonIndex === -1) { - return { text }; // return early if the word does not include an octicon - } - - return doParseOcticons(text, firstOcticonIndex); -} - -function doParseOcticons(text: string, firstOcticonIndex: number): IParsedOcticons { - const octiconOffsets: number[] = []; - let textWithoutOcticons: string = ''; - - function appendChars(chars: string) { - if (chars) { - textWithoutOcticons += chars; - - for (const _ of chars) { - octiconOffsets.push(octiconsOffset); // make sure to fill in octicon offsets - } - } - } - - let currentOcticonStart = -1; - let currentOcticonValue: string = ''; - let octiconsOffset = 0; - - let char: string; - let nextChar: string; - - let offset = firstOcticonIndex; - const length = text.length; - - // Append all characters until the first octicon - appendChars(text.substr(0, firstOcticonIndex)); - - // example: $(file-symlink-file) my cool $(other-octicon) entry - while (offset < length) { - char = text[offset]; - nextChar = text[offset + 1]; - - // beginning of octicon: some value $( <-- - if (char === octiconStartMarker[0] && nextChar === octiconStartMarker[1]) { - currentOcticonStart = offset; - - // if we had a previous potential octicon value without - // the closing ')', it was actually not an octicon and - // so we have to add it to the actual value - appendChars(currentOcticonValue); - - currentOcticonValue = octiconStartMarker; - - offset++; // jump over '(' - } - - // end of octicon: some value $(some-octicon) <-- - else if (char === ')' && currentOcticonStart !== -1) { - const currentOcticonLength = offset - currentOcticonStart + 1; // +1 to include the closing ')' - octiconsOffset += currentOcticonLength; - currentOcticonStart = -1; - currentOcticonValue = ''; - } - - // within octicon - else if (currentOcticonStart !== -1) { - currentOcticonValue += char; - } - - // any value outside of octicons - else { - appendChars(char); - } - - offset++; - } - - // if we had a previous potential octicon value without - // the closing ')', it was actually not an octicon and - // so we have to add it to the actual value - appendChars(currentOcticonValue); - - return { text: textWithoutOcticons, octiconOffsets }; -} - -export function matchesFuzzyOcticonAware(query: string, target: IParsedOcticons, enableSeparateSubstringMatching = false): IMatch[] | null { - const { text, octiconOffsets } = target; - - // Return early if there are no octicon markers in the word to match against - if (!octiconOffsets || octiconOffsets.length === 0) { - return matchesFuzzy(query, text, enableSeparateSubstringMatching); - } - - // Trim the word to match against because it could have leading - // whitespace now if the word started with an octicon - const wordToMatchAgainstWithoutOcticonsTrimmed = ltrim(text, ' '); - const leadingWhitespaceOffset = text.length - wordToMatchAgainstWithoutOcticonsTrimmed.length; - - // match on value without octicons - const matches = matchesFuzzy(query, wordToMatchAgainstWithoutOcticonsTrimmed, enableSeparateSubstringMatching); - - // Map matches back to offsets with octicons and trimming - if (matches) { - for (const match of matches) { - const octiconOffset = octiconOffsets[match.start + leadingWhitespaceOffset] /* octicon offsets at index */ + leadingWhitespaceOffset /* overall leading whitespace offset */; - match.start += octiconOffset; - match.end += octiconOffset; - } - } - - return matches; -} \ No newline at end of file diff --git a/src/vs/base/common/path.ts b/src/vs/base/common/path.ts index 5a0d15f8cd..ad915811d6 100644 --- a/src/vs/base/common/path.ts +++ b/src/vs/base/common/path.ts @@ -169,7 +169,7 @@ function _format(sep: string, pathObject: ParsedPath) { return dir + sep + base; } -interface ParsedPath { +export interface ParsedPath { root: string; dir: string; base: string; @@ -177,7 +177,7 @@ interface ParsedPath { name: string; } -interface IPath { +export interface IPath { normalize(path: string): string; isAbsolute(path: string): boolean; join(...paths: string[]): string; diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index 90bd19e2df..acb0b0d6e7 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -164,6 +164,34 @@ export const setImmediate: ISetImmediate = (function defineSetImmediate() { if (globals.setImmediate) { return globals.setImmediate.bind(globals); } + if (typeof globals.postMessage === 'function' && !globals.importScripts) { + interface IQueueElement { + id: number; + callback: () => void; + } + let pending: IQueueElement[] = []; + globals.addEventListener('message', (e: MessageEvent) => { + if (e.data && e.data.vscodeSetImmediateId) { + for (let i = 0, len = pending.length; i < len; i++) { + const candidate = pending[i]; + if (candidate.id === e.data.vscodeSetImmediateId) { + pending.splice(i, 1); + candidate.callback(); + return; + } + } + } + }); + let lastId = 0; + return (callback: () => void) => { + const myId = ++lastId; + pending.push({ + id: myId, + callback: callback + }); + globals.postMessage({ vscodeSetImmediateId: myId }); + }; + } if (typeof process !== 'undefined' && typeof process.nextTick === 'function') { return process.nextTick.bind(process); } diff --git a/src/vs/base/common/resourceTree.ts b/src/vs/base/common/resourceTree.ts index cb297324c1..10d13eff57 100644 --- a/src/vs/base/common/resourceTree.ts +++ b/src/vs/base/common/resourceTree.ts @@ -9,93 +9,77 @@ import { Iterator } from 'vs/base/common/iterator'; import { relativePath, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { mapValues } from 'vs/base/common/collections'; +import { PathIterator } from 'vs/base/common/map'; -export interface ILeafNode { +export interface IResourceNode { readonly uri: URI; readonly relativePath: string; readonly name: string; - readonly element: T; + readonly element: T | undefined; + readonly children: Iterator>; + readonly childrenCount: number; + readonly parent: IResourceNode | undefined; readonly context: C; + get(childName: string): IResourceNode | undefined; } -export interface IBranchNode { - readonly uri: URI; - readonly relativePath: string; - readonly name: string; - readonly size: number; - readonly children: Iterator>; - readonly parent: IBranchNode | undefined; - readonly context: C; - get(childName: string): INode | undefined; -} +class Node implements IResourceNode { -export type INode = IBranchNode | ILeafNode; + private _children = new Map>(); -// Internals - -class Node { - - @memoize - get name(): string { return paths.posix.basename(this.relativePath); } - - constructor(readonly uri: URI, readonly relativePath: string, readonly context: C) { } -} - -class BranchNode extends Node implements IBranchNode { - - private _children = new Map | LeafNode>(); - - get size(): number { + get childrenCount(): number { return this._children.size; } - get children(): Iterator | LeafNode> { + get children(): Iterator> { return Iterator.fromArray(mapValues(this._children)); } - constructor(uri: URI, relativePath: string, context: C, readonly parent: IBranchNode | undefined = undefined) { - super(uri, relativePath, context); + @memoize + get name(): string { + return paths.posix.basename(this.relativePath); } - get(path: string): BranchNode | LeafNode | undefined { + constructor( + readonly uri: URI, + readonly relativePath: string, + readonly context: C, + public element: T | undefined = undefined, + readonly parent: IResourceNode | undefined = undefined + ) { } + + get(path: string): Node | undefined { return this._children.get(path); } - set(path: string, child: BranchNode | LeafNode): void { + set(path: string, child: Node): void { this._children.set(path, child); } delete(path: string): void { this._children.delete(path); } -} -class LeafNode extends Node implements ILeafNode { - - constructor(uri: URI, path: string, context: C, readonly element: T) { - super(uri, path, context); + clear(): void { + this._children.clear(); } } -function collect(node: INode, result: T[]): T[] { - if (ResourceTree.isBranchNode(node)) { - Iterator.forEach(node.children, child => collect(child, result)); - } else { +function collect(node: IResourceNode, result: T[]): T[] { + if (typeof node.element !== 'undefined') { result.push(node.element); } + Iterator.forEach(node.children, child => collect(child, result)); + return result; } export class ResourceTree, C> { - readonly root: BranchNode; + readonly root: Node; - static isBranchNode(obj: any): obj is IBranchNode { - return obj instanceof BranchNode; - } - - static getRoot(node: IBranchNode): IBranchNode { + static getRoot(node: IResourceNode): IResourceNode { while (node.parent) { node = node.parent; } @@ -103,89 +87,101 @@ export class ResourceTree, C> { return node; } - static collect(node: INode): T[] { + static collect(node: IResourceNode): T[] { return collect(node, []); } + static isResourceNode(obj: any): obj is IResourceNode { + return obj instanceof Node; + } + constructor(context: C, rootURI: URI = URI.file('/')) { - this.root = new BranchNode(rootURI, '', context); + this.root = new Node(rootURI, '', context); } add(uri: URI, element: T): void { const key = relativePath(this.root.uri, uri) || uri.fsPath; - const parts = key.split(/[\\\/]/).filter(p => !!p); + const iterator = new PathIterator(false).reset(key); let node = this.root; let path = ''; - for (let i = 0; i < parts.length; i++) { - const name = parts[i]; + while (true) { + const name = iterator.value(); path = path + '/' + name; let child = node.get(name); if (!child) { - if (i < parts.length - 1) { - child = new BranchNode(joinPath(this.root.uri, path), path, this.root.context, node); - node.set(name, child); - } else { - child = new LeafNode(uri, path, this.root.context, element); - node.set(name, child); - return; - } - } + child = new Node( + joinPath(this.root.uri, path), + path, + this.root.context, + iterator.hasNext() ? undefined : element, + node + ); - if (!(child instanceof BranchNode)) { - if (i < parts.length - 1) { - throw new Error('Inconsistent tree: can\'t override leaf with branch.'); - } - - // replace - node.set(name, new LeafNode(uri, path, this.root.context, element)); - return; - } else if (i === parts.length - 1) { - throw new Error('Inconsistent tree: can\'t override branch with leaf.'); + node.set(name, child); + } else if (!iterator.hasNext()) { + child.element = element; } node = child; + + if (!iterator.hasNext()) { + return; + } + + iterator.next(); } } delete(uri: URI): T | undefined { const key = relativePath(this.root.uri, uri) || uri.fsPath; - const parts = key.split(/[\\\/]/).filter(p => !!p); - return this._delete(this.root, parts, 0); + const iterator = new PathIterator(false).reset(key); + return this._delete(this.root, iterator); } - private _delete(node: BranchNode, parts: string[], index: number): T | undefined { - const name = parts[index]; + private _delete(node: Node, iterator: PathIterator): T | undefined { + const name = iterator.value(); const child = node.get(name); if (!child) { return undefined; } - // not at end - if (index < parts.length - 1) { - if (child instanceof BranchNode) { - const result = this._delete(child, parts, index + 1); + if (iterator.hasNext()) { + const result = this._delete(child, iterator.next()); - if (typeof result !== 'undefined' && child.size === 0) { - node.delete(name); - } - - return result; - } else { - throw new Error('Inconsistent tree: Expected a branch, found a leaf instead.'); + if (typeof result !== 'undefined' && child.childrenCount === 0) { + node.delete(name); } - } - //at end - if (child instanceof BranchNode) { - // TODO: maybe we can allow this - throw new Error('Inconsistent tree: Expected a leaf, found a branch instead.'); + return result; } node.delete(name); return child.element; } + + clear(): void { + this.root.clear(); + } + + getNode(uri: URI): IResourceNode | undefined { + const key = relativePath(this.root.uri, uri) || uri.fsPath; + const iterator = new PathIterator(false).reset(key); + let node = this.root; + + while (true) { + const name = iterator.value(); + const child = node.get(name); + + if (!child || !iterator.hasNext()) { + return child; + } + + node = child; + iterator.next(); + } + } } diff --git a/src/vs/base/common/search.ts b/src/vs/base/common/search.ts index 892aa45eab..06dfc956f8 100644 --- a/src/vs/base/common/search.ts +++ b/src/vs/base/common/search.ts @@ -7,20 +7,19 @@ import * as strings from './strings'; export function buildReplaceStringWithCasePreserved(matches: string[] | null, pattern: string): string { if (matches && (matches[0] !== '')) { + const containsHyphens = validateSpecificSpecialCharacter(matches, pattern, '-'); + const containsUnderscores = validateSpecificSpecialCharacter(matches, pattern, '_'); + if (containsHyphens && !containsUnderscores) { + return buildReplaceStringForSpecificSpecialCharacter(matches, pattern, '-'); + } else if (!containsHyphens && containsUnderscores) { + return buildReplaceStringForSpecificSpecialCharacter(matches, pattern, '_'); + } if (matches[0].toUpperCase() === matches[0]) { return pattern.toUpperCase(); } else if (matches[0].toLowerCase() === matches[0]) { return pattern.toLowerCase(); } else if (strings.containsUppercaseCharacter(matches[0][0])) { - const containsHyphens = validateSpecificSpecialCharacter(matches, pattern, '-'); - const containsUnderscores = validateSpecificSpecialCharacter(matches, pattern, '_'); - if (containsHyphens && !containsUnderscores) { - return buildReplaceStringForSpecificSpecialCharacter(matches, pattern, '-'); - } else if (!containsHyphens && containsUnderscores) { - return buildReplaceStringForSpecificSpecialCharacter(matches, pattern, '_'); - } else { - return pattern[0].toUpperCase() + pattern.substr(1); - } + return pattern[0].toUpperCase() + pattern.substr(1); } else { // we don't understand its pattern yet. return pattern; diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index 84de11eddf..068687d2c3 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -350,21 +350,10 @@ function isAsciiLetter(code: number): boolean { } export function equalsIgnoreCase(a: string, b: string): boolean { - const len1 = a ? a.length : 0; - const len2 = b ? b.length : 0; - - if (len1 !== len2) { - return false; - } - - return doEqualsIgnoreCase(a, b); + return a.length === b.length && doEqualsIgnoreCase(a, b); } function doEqualsIgnoreCase(a: string, b: string, stopAt = a.length): boolean { - if (typeof a !== 'string' || typeof b !== 'string') { - return false; - } - for (let i = 0; i < stopAt; i++) { const codeA = a.charCodeAt(i); const codeB = b.charCodeAt(i); diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index 086a6c7151..02e4baf10c 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -93,6 +93,39 @@ export function isUndefinedOrNull(obj: any): obj is undefined | null { return isUndefined(obj) || obj === null; } +/** + * Asserts that the argument passed in is neither undefined nor null. + */ +export function assertIsDefined(arg: T | null | undefined): T { + if (isUndefinedOrNull(arg)) { + throw new Error('Assertion Failed: argument is undefined or null'); + } + + return arg; +} + +/** + * Asserts that each argument passed in is neither undefined nor null. + */ +export function assertAllDefined(t1: T1 | null | undefined, t2: T2 | null | undefined): [T1, T2]; +export function assertAllDefined(t1: T1 | null | undefined, t2: T2 | null | undefined, t3: T3 | null | undefined): [T1, T2, T3]; +export function assertAllDefined(t1: T1 | null | undefined, t2: T2 | null | undefined, t3: T3 | null | undefined, t4: T4 | null | undefined): [T1, T2, T3, T4]; +export function assertAllDefined(...args: (unknown | null | undefined)[]): unknown[] { + const result = []; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + + if (isUndefinedOrNull(arg)) { + throw new Error(`Assertion Failed: argument at index ${i} is undefined or null`); + } + + result.push(arg); + } + + return result; +} + const hasOwnProperty = Object.prototype.hasOwnProperty; /** diff --git a/src/vs/editor/common/core/uint.ts b/src/vs/base/common/uint.ts similarity index 74% rename from src/vs/editor/common/core/uint.ts rename to src/vs/base/common/uint.ts index 4532f4a22f..2351a96e8e 100644 --- a/src/vs/editor/common/core/uint.ts +++ b/src/vs/base/common/uint.ts @@ -3,32 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export class Uint8Matrix { - - private readonly _data: Uint8Array; - public readonly rows: number; - public readonly cols: number; - - constructor(rows: number, cols: number, defaultValue: number) { - const data = new Uint8Array(rows * cols); - for (let i = 0, len = rows * cols; i < len; i++) { - data[i] = defaultValue; - } - - this._data = data; - this.rows = rows; - this.cols = cols; - } - - public get(row: number, col: number): number { - return this._data[row * this.cols + col]; - } - - public set(row: number, col: number, value: number): void { - this._data[row * this.cols + col] = value; - } -} - export const enum Constants { /** * MAX SMI (SMall Integer) as defined in v8. diff --git a/src/vs/base/common/uri.ts b/src/vs/base/common/uri.ts index e66e27e7f5..43019a9338 100644 --- a/src/vs/base/common/uri.ts +++ b/src/vs/base/common/uri.ts @@ -10,26 +10,11 @@ const _schemePattern = /^\w[\w\d+.-]*$/; const _singleSlashStart = /^\//; const _doubleSlashStart = /^\/\//; -let _throwOnMissingSchema: boolean = true; - -/** - * @internal - */ -export function setUriThrowOnMissingScheme(value: boolean): boolean { - const old = _throwOnMissingSchema; - _throwOnMissingSchema = value; - return old; -} - -function _validateUri(ret: URI, _strict?: boolean): void { +function _validateUri(ret: URI): void { // scheme, must be set if (!ret.scheme) { - if (_strict || _throwOnMissingSchema) { - throw new Error(`[UriError]: Scheme is missing: {scheme: "", authority: "${ret.authority}", path: "${ret.path}", query: "${ret.query}", fragment: "${ret.fragment}"}`); - } else { - console.warn(`[UriError]: Scheme is missing: {scheme: "", authority: "${ret.authority}", path: "${ret.path}", query: "${ret.query}", fragment: "${ret.fragment}"}`); - } + throw new Error(`[UriError]: Scheme is missing: {scheme: "", authority: "${ret.authority}", path: "${ret.path}", query: "${ret.query}", fragment: "${ret.fragment}"}`); } // scheme, https://tools.ietf.org/html/rfc3986#section-3.1 @@ -56,14 +41,8 @@ function _validateUri(ret: URI, _strict?: boolean): void { } } -// for a while we allowed uris *without* schemes and this is the migration -// for them, e.g. an uri without scheme and without strict-mode warns and falls -// back to the file-scheme. that should cause the least carnage and still be a -// clear warning -function _schemeFix(scheme: string, _strict: boolean): string { - if (_strict || _throwOnMissingSchema) { - return scheme || _empty; - } +// graceful behaviour when scheme is missing: fallback to using 'file'-scheme +function _schemeFix(scheme: string): string { if (!scheme) { console.trace('BAD uri lacks scheme, falling back to file-scheme.'); scheme = 'file'; @@ -159,7 +138,7 @@ export class URI implements UriComponents { /** * @internal */ - protected constructor(scheme: string, authority?: string, path?: string, query?: string, fragment?: string, _strict?: boolean); + protected constructor(scheme: string, authority?: string, path?: string, query?: string, fragment?: string); /** * @internal @@ -169,7 +148,7 @@ export class URI implements UriComponents { /** * @internal */ - protected constructor(schemeOrData: string | UriComponents, authority?: string, path?: string, query?: string, fragment?: string, _strict: boolean = false) { + protected constructor(schemeOrData: string | UriComponents, authority?: string, path?: string, query?: string, fragment?: string) { if (typeof schemeOrData === 'object') { this.scheme = schemeOrData.scheme || _empty; @@ -181,13 +160,13 @@ export class URI implements UriComponents { // that creates uri components. // _validateUri(this); } else { - this.scheme = _schemeFix(schemeOrData, _strict); + this.scheme = _schemeFix(schemeOrData); this.authority = authority || _empty; this.path = _referenceResolution(this.scheme, path || _empty); this.query = query || _empty; this.fragment = fragment || _empty; - _validateUri(this, _strict); + _validateUri(this); } } @@ -226,7 +205,7 @@ export class URI implements UriComponents { // ---- modify to new ------------------------- - with(change: { scheme?: string; authority?: string | null; path?: string | null; query?: string | null; fragment?: string | null }): URI { + with(change: { scheme?: string; authority?: string | null; path?: string | null; query?: string | null; fragment?: string | null; }): URI { if (!change) { return this; @@ -279,7 +258,7 @@ export class URI implements UriComponents { * * @param value A string which represents an URI (see `URI#toString`). */ - static parse(value: string, _strict: boolean = false): URI { + static parse(value: string): URI { const match = _regexp.exec(value); if (!match) { return new _URI(_empty, _empty, _empty, _empty, _empty); @@ -289,8 +268,7 @@ export class URI implements UriComponents { decodeURIComponent(match[4] || _empty), decodeURIComponent(match[5] || _empty), decodeURIComponent(match[7] || _empty), - decodeURIComponent(match[9] || _empty), - _strict + decodeURIComponent(match[9] || _empty) ); } @@ -342,7 +320,7 @@ export class URI implements UriComponents { return new _URI('file', authority, path, _empty, _empty); } - static from(components: { scheme: string; authority?: string; path?: string; query?: string; fragment?: string }): URI { + static from(components: { scheme: string; authority?: string; path?: string; query?: string; fragment?: string; }): URI { return new _URI( components.scheme, components.authority, @@ -467,7 +445,7 @@ class _URI extends URI { } // reserved characters: https://tools.ietf.org/html/rfc3986#section-2.2 -const encodeTable: { [ch: number]: string } = { +const encodeTable: { [ch: number]: string; } = { [CharCode.Colon]: '%3A', // gen-delims [CharCode.Slash]: '%2F', [CharCode.QuestionMark]: '%3F', diff --git a/src/vs/base/node/encoding.ts b/src/vs/base/node/encoding.ts index 6e0e8dbb3b..68f08c8424 100644 --- a/src/vs/base/node/encoding.ts +++ b/src/vs/base/node/encoding.ts @@ -122,7 +122,11 @@ export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions // detection. thus, wrap up starting the stream even // without all the data to get things going else { - this._startDecodeStream(() => this.decodeStream!.end(callback)); + this._startDecodeStream(() => { + if (this.decodeStream) { + this.decodeStream.end(callback); + } + }); } } }; diff --git a/src/vs/base/node/languagePacks.d.ts b/src/vs/base/node/languagePacks.d.ts index 1bb78dae05..979ae4b6d6 100644 --- a/src/vs/base/node/languagePacks.d.ts +++ b/src/vs/base/node/languagePacks.d.ts @@ -9,6 +9,7 @@ export interface NLSConfiguration { [key: string]: string; }; pseudo?: boolean; + _languagePackSupport?: boolean; } export interface InternalNLSConfiguration extends NLSConfiguration { diff --git a/src/vs/base/node/macAddress.ts b/src/vs/base/node/macAddress.ts index 9889a3e420..a2bf8372cc 100644 --- a/src/vs/base/node/macAddress.ts +++ b/src/vs/base/node/macAddress.ts @@ -11,21 +11,15 @@ const cmdline = { unix: '/sbin/ifconfig -a || /sbin/ip link' }; -const invalidMacAddresses = [ +const invalidMacAddresses = new Set([ '00:00:00:00:00:00', 'ff:ff:ff:ff:ff:ff', 'ac:de:48:00:11:22' -]; +]); function validateMacAddress(candidate: string): boolean { - let tempCandidate = candidate.replace(/\-/g, ':').toLowerCase(); - for (let invalidMacAddress of invalidMacAddresses) { - if (invalidMacAddress === tempCandidate) { - return false; - } - } - - return true; + const tempCandidate = candidate.replace(/\-/g, ':').toLowerCase(); + return !invalidMacAddresses.has(tempCandidate); } export function getMac(): Promise { @@ -66,4 +60,4 @@ function doGetMac(): Promise { reject(err); } }); -} \ No newline at end of file +} diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts index 9313a89adb..b7a9b15343 100644 --- a/src/vs/base/node/pfs.ts +++ b/src/vs/base/node/pfs.ts @@ -138,6 +138,20 @@ export async function readdir(path: string): Promise { return handleDirectoryChildren(await promisify(fs.readdir)(path)); } +export async function readdirWithFileTypes(path: string): Promise { + const children = await promisify(fs.readdir)(path, { withFileTypes: true }); + + // Mac: uses NFD unicode form on disk, but we want NFC + // See also https://github.com/nodejs/node/issues/2165 + if (platform.isMacintosh) { + for (const child of children) { + child.name = normalizeNFC(child.name); + } + } + + return children; +} + export function readdirSync(path: string): string[] { return handleDirectoryChildren(fs.readdirSync(path)); } @@ -679,4 +693,4 @@ const WIN32_MAX_HEAP_SIZE = 700 * 1024 * 1024; // 700 MB const GENERAL_MAX_HEAP_SIZE = 700 * 2 * 1024 * 1024; // 1400 MB export const MAX_FILE_SIZE = process.arch === 'ia32' ? WIN32_MAX_FILE_SIZE : GENERAL_MAX_FILE_SIZE; -export const MAX_HEAP_SIZE = process.arch === 'ia32' ? WIN32_MAX_HEAP_SIZE : GENERAL_MAX_HEAP_SIZE; \ No newline at end of file +export const MAX_HEAP_SIZE = process.arch === 'ia32' ? WIN32_MAX_HEAP_SIZE : GENERAL_MAX_HEAP_SIZE; diff --git a/src/vs/base/node/processes.ts b/src/vs/base/node/processes.ts index fa93ee0f00..21f96596ec 100644 --- a/src/vs/base/node/processes.ts +++ b/src/vs/base/node/processes.ts @@ -40,7 +40,7 @@ function getWindowsCode(status: number): TerminateResponseCode { } } -export function terminateProcess(process: cp.ChildProcess, cwd?: string): TerminateResponse { +function terminateProcess(process: cp.ChildProcess, cwd?: string): Promise { if (Platform.isWindows) { try { const options: any = { @@ -49,24 +49,41 @@ export function terminateProcess(process: cp.ChildProcess, cwd?: string): Termin if (cwd) { options.cwd = cwd; } - cp.execFileSync('taskkill', ['/T', '/F', '/PID', process.pid.toString()], options); + const killProcess = cp.execFile('taskkill', ['/T', '/F', '/PID', process.pid.toString()], options); + return new Promise((resolve, reject) => { + killProcess.once('error', (err) => { + resolve({ success: false, error: err }); + }); + killProcess.once('exit', (code, signal) => { + if (code === 0) { + resolve({ success: true }); + } else { + resolve({ success: false, code: code !== null ? code : TerminateResponseCode.Unknown }); + } + }); + }); } catch (err) { - return { success: false, error: err, code: err.status ? getWindowsCode(err.status) : TerminateResponseCode.Unknown }; + return Promise.resolve({ success: false, error: err, code: err.status ? getWindowsCode(err.status) : TerminateResponseCode.Unknown }); } } else if (Platform.isLinux || Platform.isMacintosh) { try { const cmd = getPathFromAmdModule(require, 'vs/base/node/terminateProcess.sh'); - const result = cp.spawnSync(cmd, [process.pid.toString()]); - if (result.error) { - return { success: false, error: result.error }; - } + return new Promise((resolve, reject) => { + cp.execFile(cmd, [process.pid.toString()], { encoding: 'utf8', shell: true } as cp.ExecFileOptions, (err, stdout, stderr) => { + if (err) { + resolve({ success: false, error: err }); + } else { + resolve({ success: true }); + } + }); + }); } catch (err) { - return { success: false, error: err }; + return Promise.resolve({ success: false, error: err }); } } else { process.kill('SIGKILL'); } - return { success: true }; + return Promise.resolve({ success: true }); } export function getWindowsShell(): string { @@ -122,6 +139,7 @@ export abstract class AbstractProcess { } this.childProcess = null; + this.childProcessPromise = null; this.terminateRequested = false; if (this.options.env) { @@ -288,11 +306,12 @@ export abstract class AbstractProcess { } return this.childProcessPromise.then((childProcess) => { this.terminateRequested = true; - const result = terminateProcess(childProcess, this.options.cwd); - if (result.success) { - this.childProcess = null; - } - return result; + return terminateProcess(childProcess, this.options.cwd).then(response => { + if (response.success) { + this.childProcess = null; + } + return response; + }); }, (err) => { return { success: true }; }); @@ -316,13 +335,16 @@ export abstract class AbstractProcess { export class LineProcess extends AbstractProcess { - private stdoutLineDecoder: LineDecoder; - private stderrLineDecoder: LineDecoder; + private stdoutLineDecoder: LineDecoder | null; + private stderrLineDecoder: LineDecoder | null; public constructor(executable: Executable); public constructor(cmd: string, args: string[], shell: boolean, options: CommandOptions); public constructor(arg1: string | Executable, arg2?: string[], arg3?: boolean | ForkOptions, arg4?: CommandOptions) { super(arg1, arg2, arg3, arg4); + + this.stdoutLineDecoder = null; + this.stderrLineDecoder = null; } protected handleExec(cc: ValueCallback, pp: ProgressCallback, error: Error, stdout: Buffer, stderr: Buffer) { @@ -341,24 +363,30 @@ export class LineProcess extends AbstractProcess { } protected handleSpawn(childProcess: cp.ChildProcess, cc: ValueCallback, pp: ProgressCallback, ee: ErrorCallback, sync: boolean): void { - this.stdoutLineDecoder = new LineDecoder(); - this.stderrLineDecoder = new LineDecoder(); + const stdoutLineDecoder = new LineDecoder(); + const stderrLineDecoder = new LineDecoder(); childProcess.stdout.on('data', (data: Buffer) => { - const lines = this.stdoutLineDecoder.write(data); + const lines = stdoutLineDecoder.write(data); lines.forEach(line => pp({ line: line, source: Source.stdout })); }); childProcess.stderr.on('data', (data: Buffer) => { - const lines = this.stderrLineDecoder.write(data); + const lines = stderrLineDecoder.write(data); lines.forEach(line => pp({ line: line, source: Source.stderr })); }); + + this.stdoutLineDecoder = stdoutLineDecoder; + this.stderrLineDecoder = stderrLineDecoder; } protected handleClose(data: any, cc: ValueCallback, pp: ProgressCallback, ee: ErrorCallback): void { - [this.stdoutLineDecoder.end(), this.stderrLineDecoder.end()].forEach((line, index) => { - if (line) { - pp({ line: line, source: index === 0 ? Source.stdout : Source.stderr }); - } - }); + const stdoutLine = this.stdoutLineDecoder ? this.stdoutLineDecoder.end() : null; + if (stdoutLine) { + pp({ line: stdoutLine, source: Source.stdout }); + } + const stderrLine = this.stderrLineDecoder ? this.stderrLineDecoder.end() : null; + if (stderrLine) { + pp({ line: stderrLine, source: Source.stderr }); + } } } diff --git a/src/vs/base/node/zip.ts b/src/vs/base/node/zip.ts index bc2360baa2..320e2b2670 100644 --- a/src/vs/base/node/zip.ts +++ b/src/vs/base/node/zip.ts @@ -156,7 +156,7 @@ function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, tok const stream = openZipStream(zipfile, entry); const mode = modeFromEntry(entry); - last = createCancelablePromise(token => throttler.queue(() => stream.then(stream => extractEntry(stream, fileName, mode, targetPath, options, token).then(() => readNextEntry(token)))).then(null!, e)); + last = createCancelablePromise(token => throttler.queue(() => stream.then(stream => extractEntry(stream, fileName, mode, targetPath, options, token).then(() => readNextEntry(token)))).then(null, e)); }); }); } diff --git a/src/vs/base/parts/contextmenu/electron-browser/contextmenu.ts b/src/vs/base/parts/contextmenu/electron-browser/contextmenu.ts index 7afb10ca21..eca7a8bee3 100644 --- a/src/vs/base/parts/contextmenu/electron-browser/contextmenu.ts +++ b/src/vs/base/parts/contextmenu/electron-browser/contextmenu.ts @@ -28,7 +28,7 @@ export function popup(items: IContextMenuItem[], options?: IPopupOptions): void ipcRenderer.removeListener(onClickChannel, onClickChannelHandler); - if (options && options.onHide) { + if (options?.onHide) { options.onHide(); } }); @@ -55,4 +55,4 @@ function createItem(item: IContextMenuItem, processedItems: IContextMenuItem[]): } return serializableItem; -} \ No newline at end of file +} diff --git a/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts b/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts index 23473492e1..6d7fc4c11c 100644 --- a/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts +++ b/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Menu, MenuItem, BrowserWindow, ipcMain, Event as IpcMainEvent } from 'electron'; +import { Menu, MenuItem, BrowserWindow, ipcMain, IpcMainEvent } from 'electron'; import { ISerializableContextMenuItem, CONTEXT_MENU_CLOSE_CHANNEL, CONTEXT_MENU_CHANNEL, IPopupOptions } from 'vs/base/parts/contextmenu/common/contextmenu'; export function registerContextMenuListener(): void { diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index 43d6e38c7c..c53a5cd494 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -8,7 +8,6 @@ import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/li import { CancelablePromise, createCancelablePromise, timeout } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; -import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc'; import { VSBuffer } from 'vs/base/common/buffer'; /** diff --git a/src/vs/base/parts/ipc/electron-main/ipc.electron-main.ts b/src/vs/base/parts/ipc/electron-main/ipc.electron-main.ts index 7f669be329..09e271f9bc 100644 --- a/src/vs/base/parts/ipc/electron-main/ipc.electron-main.ts +++ b/src/vs/base/parts/ipc/electron-main/ipc.electron-main.ts @@ -24,7 +24,7 @@ function createScopedOnMessageEvent(senderId: number, eventName: string): Event< export class Server extends IPCServer { - private static Clients = new Map(); + private static readonly Clients = new Map(); private static getOnDidClientConnect(): Event { const onHello = Event.fromNodeEventEmitter(ipcMain, 'ipc:hello', ({ sender }) => sender); diff --git a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts index 2df76a4aef..5bf24732ca 100644 --- a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts +++ b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts @@ -53,7 +53,7 @@ export const QuickOpenItemAccessor = new QuickOpenItemAccessorClass(); export class QuickOpenEntry { private id: string; - private labelHighlights: IHighlight[]; + private labelHighlights?: IHighlight[]; private descriptionHighlights?: IHighlight[]; private detailHighlights?: IHighlight[]; private hidden: boolean | undefined; @@ -160,7 +160,7 @@ export class QuickOpenEntry { /** * Allows to set highlight ranges that should show up for the entry label and optionally description if set. */ - setHighlights(labelHighlights: IHighlight[], descriptionHighlights?: IHighlight[], detailHighlights?: IHighlight[]): void { + setHighlights(labelHighlights?: IHighlight[], descriptionHighlights?: IHighlight[], detailHighlights?: IHighlight[]): void { this.labelHighlights = labelHighlights; this.descriptionHighlights = descriptionHighlights; this.detailHighlights = detailHighlights; @@ -169,7 +169,7 @@ export class QuickOpenEntry { /** * Allows to return highlight ranges that should show up for the entry label and description. */ - getHighlights(): [IHighlight[] /* Label */, IHighlight[] | undefined /* Description */, IHighlight[] | undefined /* Detail */] { + getHighlights(): [IHighlight[] | undefined /* Label */, IHighlight[] | undefined /* Description */, IHighlight[] | undefined /* Detail */] { return [this.labelHighlights, this.descriptionHighlights, this.detailHighlights]; } @@ -260,7 +260,7 @@ export class QuickOpenEntryGroup extends QuickOpenEntry { return this.entry; } - getHighlights(): [IHighlight[], IHighlight[] | undefined, IHighlight[] | undefined] { + getHighlights(): [IHighlight[] | undefined, IHighlight[] | undefined, IHighlight[] | undefined] { return this.entry ? this.entry.getHighlights() : super.getHighlights(); } @@ -268,7 +268,7 @@ export class QuickOpenEntryGroup extends QuickOpenEntry { return this.entry ? this.entry.isHidden() : super.isHidden(); } - setHighlights(labelHighlights: IHighlight[], descriptionHighlights?: IHighlight[], detailHighlights?: IHighlight[]): void { + setHighlights(labelHighlights?: IHighlight[], descriptionHighlights?: IHighlight[], detailHighlights?: IHighlight[]): void { this.entry ? this.entry.setHighlights(labelHighlights, descriptionHighlights, detailHighlights) : super.setHighlights(labelHighlights, descriptionHighlights, detailHighlights); } @@ -351,7 +351,7 @@ class Renderer implements IRenderer { row1.appendChild(icon); // Label - const label = new IconLabel(row1, { supportHighlights: true, supportDescriptionHighlights: true, supportOcticons: true }); + const label = new IconLabel(row1, { supportHighlights: true, supportDescriptionHighlights: true, supportCodicons: true }); // Keybinding const keybindingContainer = document.createElement('span'); @@ -434,7 +434,7 @@ class Renderer implements IRenderer { } } else { DOM.removeClass(groupData.container, 'results-group-separator'); - groupData.container.style.borderTopColor = null; + groupData.container.style.borderTopColor = ''; } // Group Label diff --git a/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts b/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts index 9bf50bb89c..b65e1a39f5 100644 --- a/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts +++ b/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts @@ -99,25 +99,40 @@ export class QuickOpenWidget extends Disposable implements IModelProvider { private isDisposed: boolean; private options: IQuickOpenOptions; + // @ts-ignore (legacy widget - to be replaced with quick input) private element: HTMLElement; + // @ts-ignore (legacy widget - to be replaced with quick input) private tree: ITree; + // @ts-ignore (legacy widget - to be replaced with quick input) private inputBox: InputBox; + // @ts-ignore (legacy widget - to be replaced with quick input) private inputContainer: HTMLElement; + // @ts-ignore (legacy widget - to be replaced with quick input) private helpText: HTMLElement; + // @ts-ignore (legacy widget - to be replaced with quick input) private resultCount: HTMLElement; + // @ts-ignore (legacy widget - to be replaced with quick input) private treeContainer: HTMLElement; + // @ts-ignore (legacy widget - to be replaced with quick input) private progressBar: ProgressBar; + // @ts-ignore (legacy widget - to be replaced with quick input) private visible: boolean; + // @ts-ignore (legacy widget - to be replaced with quick input) private isLoosingFocus: boolean; private callbacks: IQuickOpenCallbacks; private quickNavigateConfiguration: IQuickNavigateConfiguration | undefined; private container: HTMLElement; + // @ts-ignore (legacy widget - to be replaced with quick input) private treeElement: HTMLElement; + // @ts-ignore (legacy widget - to be replaced with quick input) private inputElement: HTMLElement; + // @ts-ignore (legacy widget - to be replaced with quick input) private layoutDimensions: DOM.Dimension; private model: IModel | null; private inputChangingTimeoutHandle: any; + // @ts-ignore (legacy widget - to be replaced with quick input) private styles: IQuickOpenStyles; + // @ts-ignore (legacy widget - to be replaced with quick input) private renderer: Renderer; constructor(container: HTMLElement, callbacks: IQuickOpenCallbacks, options: IQuickOpenOptions) { @@ -381,16 +396,16 @@ export class QuickOpenWidget extends Disposable implements IModelProvider { protected applyStyles(): void { if (this.element) { const foreground = this.styles.foreground ? this.styles.foreground.toString() : null; - const background = this.styles.background ? this.styles.background.toString() : null; - const borderColor = this.styles.borderColor ? this.styles.borderColor.toString() : null; - const widgetShadow = this.styles.widgetShadow ? this.styles.widgetShadow.toString() : null; + const background = this.styles.background ? this.styles.background.toString() : ''; + const borderColor = this.styles.borderColor ? this.styles.borderColor.toString() : ''; + const widgetShadow = this.styles.widgetShadow ? this.styles.widgetShadow.toString() : ''; this.element.style.color = foreground; this.element.style.backgroundColor = background; this.element.style.borderColor = borderColor; - this.element.style.borderWidth = borderColor ? '1px' : null; - this.element.style.borderStyle = borderColor ? 'solid' : null; - this.element.style.boxShadow = widgetShadow ? `0 5px 8px ${widgetShadow}` : null; + this.element.style.borderWidth = borderColor ? '1px' : ''; + this.element.style.borderStyle = borderColor ? 'solid' : ''; + this.element.style.boxShadow = widgetShadow ? `0 5px 8px ${widgetShadow}` : ''; } if (this.progressBar) { diff --git a/src/vs/base/parts/storage/node/storage.ts b/src/vs/base/parts/storage/node/storage.ts index 4d4d812f77..024fb6137d 100644 --- a/src/vs/base/parts/storage/node/storage.ts +++ b/src/vs/base/parts/storage/node/storage.ts @@ -32,12 +32,12 @@ export interface ISQLiteStorageDatabaseLoggingOptions { export class SQLiteStorageDatabase implements IStorageDatabase { - static IN_MEMORY_PATH = ':memory:'; + static readonly IN_MEMORY_PATH = ':memory:'; get onDidChangeItemsExternal(): Event { return Event.None; } // since we are the only client, there can be no external changes - private static BUSY_OPEN_TIMEOUT = 2000; // timeout in ms to retry when opening DB fails with SQLITE_BUSY - private static MAX_HOST_PARAMETERS = 256; // maximum number of parameters within a statement + private static readonly BUSY_OPEN_TIMEOUT = 2000; // timeout in ms to retry when opening DB fails with SQLITE_BUSY + private static readonly MAX_HOST_PARAMETERS = 256; // maximum number of parameters within a statement private path: string; private name: string; @@ -82,16 +82,18 @@ export class SQLiteStorageDatabase implements IStorageDatabase { } return this.transaction(connection, () => { + const toInsert = request.insert; + const toDelete = request.delete; // INSERT - if (request.insert && request.insert.size > 0) { + if (toInsert && toInsert.size > 0) { const keysValuesChunks: (string[])[] = []; keysValuesChunks.push([]); // seed with initial empty chunk // Split key/values into chunks of SQLiteStorageDatabase.MAX_HOST_PARAMETERS // so that we can efficiently run the INSERT with as many HOST parameters as possible let currentChunkIndex = 0; - request.insert.forEach((value, key) => { + toInsert.forEach((value, key) => { let keyValueChunk = keysValuesChunks[currentChunkIndex]; if (keyValueChunk.length > SQLiteStorageDatabase.MAX_HOST_PARAMETERS) { @@ -107,7 +109,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { this.prepare(connection, `INSERT INTO ItemTable VALUES ${fill(keysValuesChunk.length / 2, '(?,?)').join(',')}`, stmt => stmt.run(keysValuesChunk), () => { const keys: string[] = []; let length = 0; - request.insert!.forEach((value, key) => { + toInsert.forEach((value, key) => { keys.push(key); length += value.length; }); @@ -118,7 +120,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { } // DELETE - if (request.delete && request.delete.size) { + if (toDelete && toDelete.size) { const keysChunks: (string[])[] = []; keysChunks.push([]); // seed with initial empty chunk @@ -126,7 +128,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { // so that we can efficiently run the DELETE with as many HOST parameters // as possible let currentChunkIndex = 0; - request.delete.forEach(key => { + toDelete.forEach(key => { let keyChunk = keysChunks[currentChunkIndex]; if (keyChunk.length > SQLiteStorageDatabase.MAX_HOST_PARAMETERS) { @@ -141,7 +143,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { keysChunks.forEach(keysChunk => { this.prepare(connection, `DELETE FROM ItemTable WHERE key IN (${fill(keysChunk.length, '?').join(',')})`, stmt => stmt.run(keysChunk), () => { const keys: string[] = []; - request.delete!.forEach(key => { + toDelete.forEach(key => { keys.push(key); }); diff --git a/src/vs/base/parts/tree/browser/treeView.ts b/src/vs/base/parts/tree/browser/treeView.ts index 9f93fa48ee..77664bff71 100644 --- a/src/vs/base/parts/tree/browser/treeView.ts +++ b/src/vs/base/parts/tree/browser/treeView.ts @@ -398,8 +398,8 @@ function reactionEquals(one: _.IDragOverReaction, other: _.IDragOverReaction | n export class TreeView extends HeightMap { - static BINDING = 'monaco-tree-row'; - static LOADING_DECORATION_DELAY = 800; + static readonly BINDING = 'monaco-tree-row'; + static readonly LOADING_DECORATION_DELAY = 800; private static counter: number = 0; private instance: number; @@ -925,16 +925,15 @@ export class TreeView extends HeightMap { if (!skipDiff) { const lcs = new Diff.LcsDiff( { - getLength: () => previousChildrenIds.length, - getElementAtIndex: (i: number) => previousChildrenIds[i] - }, { - getLength: () => afterModelItems.length, - getElementAtIndex: (i: number) => afterModelItems[i].id - }, + getElements: () => previousChildrenIds + }, + { + getElements: () => afterModelItems.map(item => item.id) + }, null ); - diff = lcs.ComputeDiff(false); + diff = lcs.ComputeDiff(false).changes; // this means that the result of the diff algorithm would result // in inserting items that were already registered. this can only diff --git a/src/vs/base/test/common/cancellation.test.ts b/src/vs/base/test/common/cancellation.test.ts index 1998737415..8c78270a47 100644 --- a/src/vs/base/test/common/cancellation.test.ts +++ b/src/vs/base/test/common/cancellation.test.ts @@ -95,6 +95,20 @@ suite('CancellationToken', function () { assert.equal(count, 0); }); + test('dispose calls no listeners (unless told to cancel)', function () { + + let count = 0; + + let source = new CancellationTokenSource(); + source.token.onCancellationRequested(function () { + count += 1; + }); + + source.dispose(true); + // source.cancel(); + assert.equal(count, 1); + }); + test('parent cancels child', function () { let parent = new CancellationTokenSource(); diff --git a/src/vs/base/test/common/octicon.test.ts b/src/vs/base/test/common/codicon.test.ts similarity index 51% rename from src/vs/base/test/common/octicon.test.ts rename to src/vs/base/test/common/codicon.test.ts index b25532f353..be8708e57c 100644 --- a/src/vs/base/test/common/octicon.test.ts +++ b/src/vs/base/test/common/codicon.test.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { IMatch } from 'vs/base/common/filters'; -import { matchesFuzzyOcticonAware, parseOcticons } from 'vs/base/common/octicon'; +import { matchesFuzzyCodiconAware, parseCodicons, IParsedCodicons } from 'vs/base/common/codicon'; -export interface IOcticonFilter { +export interface ICodiconFilter { // Returns null if word doesn't match. - (query: string, target: { text: string, octiconOffsets?: number[] }): IMatch[] | null; + (query: string, target: IParsedCodicons): IMatch[] | null; } -function filterOk(filter: IOcticonFilter, word: string, target: { text: string, octiconOffsets?: number[] }, highlights?: { start: number; end: number; }[]) { +function filterOk(filter: ICodiconFilter, word: string, target: IParsedCodicons, highlights?: { start: number; end: number; }[]) { let r = filter(word, target); assert(r); if (highlights) { @@ -19,24 +19,24 @@ function filterOk(filter: IOcticonFilter, word: string, target: { text: string, } } -suite('Octicon', () => { - test('matchesFuzzzyOcticonAware', () => { +suite('Codicon', () => { + test('matchesFuzzzyCodiconAware', () => { // Camel Case - filterOk(matchesFuzzyOcticonAware, 'ccr', parseOcticons('$(octicon)CamelCaseRocks$(octicon)'), [ + filterOk(matchesFuzzyCodiconAware, 'ccr', parseCodicons('$(codicon)CamelCaseRocks$(codicon)'), [ { start: 10, end: 11 }, { start: 15, end: 16 }, { start: 19, end: 20 } ]); - filterOk(matchesFuzzyOcticonAware, 'ccr', parseOcticons('$(octicon) CamelCaseRocks $(octicon)'), [ + filterOk(matchesFuzzyCodiconAware, 'ccr', parseCodicons('$(codicon) CamelCaseRocks $(codicon)'), [ { start: 11, end: 12 }, { start: 16, end: 17 }, { start: 20, end: 21 } ]); - filterOk(matchesFuzzyOcticonAware, 'iut', parseOcticons('$(octicon) Indent $(octico) Using $(octic) Tpaces'), [ + filterOk(matchesFuzzyCodiconAware, 'iut', parseCodicons('$(codicon) Indent $(octico) Using $(octic) Tpaces'), [ { start: 11, end: 12 }, { start: 28, end: 29 }, { start: 43, end: 44 }, @@ -44,22 +44,22 @@ suite('Octicon', () => { // Prefix - filterOk(matchesFuzzyOcticonAware, 'using', parseOcticons('$(octicon) Indent Using Spaces'), [ + filterOk(matchesFuzzyCodiconAware, 'using', parseCodicons('$(codicon) Indent Using Spaces'), [ { start: 18, end: 23 }, ]); - // Broken Octicon + // Broken Codicon - filterOk(matchesFuzzyOcticonAware, 'octicon', parseOcticons('This $(octicon Indent Using Spaces'), [ + filterOk(matchesFuzzyCodiconAware, 'codicon', parseCodicons('This $(codicon Indent Using Spaces'), [ { start: 7, end: 14 }, ]); - filterOk(matchesFuzzyOcticonAware, 'indent', parseOcticons('This $octicon Indent Using Spaces'), [ + filterOk(matchesFuzzyCodiconAware, 'indent', parseCodicons('This $codicon Indent Using Spaces'), [ { start: 14, end: 20 }, ]); // Testing #59343 - filterOk(matchesFuzzyOcticonAware, 'unt', parseOcticons('$(primitive-dot) $(file-text) Untitled-1'), [ + filterOk(matchesFuzzyCodiconAware, 'unt', parseCodicons('$(primitive-dot) $(file-text) Untitled-1'), [ { start: 30, end: 33 }, ]); }); diff --git a/src/vs/base/test/common/color.test.ts b/src/vs/base/test/common/color.test.ts index 406ef624a6..6ad35f1557 100644 --- a/src/vs/base/test/common/color.test.ts +++ b/src/vs/base/test/common/color.test.ts @@ -196,7 +196,6 @@ suite('Color', () => { test('parseHex', () => { // invalid - assert.deepEqual(Color.Format.CSS.parseHex(null!), null); assert.deepEqual(Color.Format.CSS.parseHex(''), null); assert.deepEqual(Color.Format.CSS.parseHex('#'), null); assert.deepEqual(Color.Format.CSS.parseHex('#0102030'), null); @@ -243,4 +242,4 @@ suite('Color', () => { }); }); }); -}); \ No newline at end of file +}); diff --git a/src/vs/base/test/common/diff/diff.test.ts b/src/vs/base/test/common/diff/diff.test.ts index 568af915e8..a098cdebf2 100644 --- a/src/vs/base/test/common/diff/diff.test.ts +++ b/src/vs/base/test/common/diff/diff.test.ts @@ -4,21 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { LcsDiff, IDiffChange, ISequence } from 'vs/base/common/diff/diff'; - -class StringDiffSequence implements ISequence { - - constructor(private source: string) { - } - - getLength() { - return this.source.length; - } - - getElementAtIndex(i: number) { - return this.source.charCodeAt(i); - } -} +import { LcsDiff, IDiffChange, StringDiffSequence } from 'vs/base/common/diff/diff'; function createArray(length: number, value: T): T[] { const r: T[] = []; @@ -71,9 +57,9 @@ function assertAnswer(originalStr: string, modifiedStr: string, changes: IDiffCh } } -function lcsInnerTest(Algorithm: any, originalStr: string, modifiedStr: string, answerStr: string, onlyLength: boolean = false): void { - let diff = new Algorithm(new StringDiffSequence(originalStr), new StringDiffSequence(modifiedStr)); - let changes = diff.ComputeDiff(); +function lcsInnerTest(originalStr: string, modifiedStr: string, answerStr: string, onlyLength: boolean = false): void { + let diff = new LcsDiff(new StringDiffSequence(originalStr), new StringDiffSequence(modifiedStr)); + let changes = diff.ComputeDiff(false).changes; assertAnswer(originalStr, modifiedStr, changes, answerStr, onlyLength); } @@ -85,32 +71,28 @@ function stringPower(str: string, power: number): string { return r; } -function lcsTest(Algorithm: any, originalStr: string, modifiedStr: string, answerStr: string) { - lcsInnerTest(Algorithm, originalStr, modifiedStr, answerStr); +function lcsTest(originalStr: string, modifiedStr: string, answerStr: string) { + lcsInnerTest(originalStr, modifiedStr, answerStr); for (let i = 2; i <= 5; i++) { - lcsInnerTest(Algorithm, stringPower(originalStr, i), stringPower(modifiedStr, i), stringPower(answerStr, i), true); + lcsInnerTest(stringPower(originalStr, i), stringPower(modifiedStr, i), stringPower(answerStr, i), true); } } -function lcsTests(Algorithm: any) { - lcsTest(Algorithm, 'heLLo world', 'hello orlando', 'heo orld'); - lcsTest(Algorithm, 'abcde', 'acd', 'acd'); // simple - lcsTest(Algorithm, 'abcdbce', 'bcede', 'bcde'); // skip - lcsTest(Algorithm, 'abcdefgabcdefg', 'bcehafg', 'bceafg'); // long - lcsTest(Algorithm, 'abcde', 'fgh', ''); // no match - lcsTest(Algorithm, 'abcfabc', 'fabc', 'fabc'); - lcsTest(Algorithm, '0azby0', '9axbzby9', 'azby'); - lcsTest(Algorithm, '0abc00000', '9a1b2c399999', 'abc'); - - lcsTest(Algorithm, 'fooBar', 'myfooBar', 'fooBar'); // all insertions - lcsTest(Algorithm, 'fooBar', 'fooMyBar', 'fooBar'); // all insertions - lcsTest(Algorithm, 'fooBar', 'fooBar', 'fooBar'); // identical sequences -} - suite('Diff', () => { test('LcsDiff - different strings tests', function () { this.timeout(10000); - lcsTests(LcsDiff); + lcsTest('heLLo world', 'hello orlando', 'heo orld'); + lcsTest('abcde', 'acd', 'acd'); // simple + lcsTest('abcdbce', 'bcede', 'bcde'); // skip + lcsTest('abcdefgabcdefg', 'bcehafg', 'bceafg'); // long + lcsTest('abcde', 'fgh', ''); // no match + lcsTest('abcfabc', 'fabc', 'fabc'); + lcsTest('0azby0', '9axbzby9', 'azby'); + lcsTest('0abc00000', '9a1b2c399999', 'abc'); + + lcsTest('fooBar', 'myfooBar', 'fooBar'); // all insertions + lcsTest('fooBar', 'fooMyBar', 'fooBar'); // all insertions + lcsTest('fooBar', 'fooBar', 'fooBar'); // identical sequences }); }); @@ -123,18 +105,17 @@ suite('Diff - Ported from VS', () => { // doesn't get there first. let predicateCallCount = 0; - let diff = new LcsDiff(new StringDiffSequence(left), new StringDiffSequence(right), function (leftIndex, leftSequence, longestMatchSoFar) { + let diff = new LcsDiff(new StringDiffSequence(left), new StringDiffSequence(right), function (leftIndex, longestMatchSoFar) { assert.equal(predicateCallCount, 0); predicateCallCount++; - assert.equal(leftSequence.getLength(), left.length); assert.equal(leftIndex, 1); // cancel processing return false; }); - let changes = diff.ComputeDiff(true); + let changes = diff.ComputeDiff(true).changes; assert.equal(predicateCallCount, 1); @@ -144,26 +125,26 @@ suite('Diff - Ported from VS', () => { // Cancel after the first match ('c') - diff = new LcsDiff(new StringDiffSequence(left), new StringDiffSequence(right), function (leftIndex, leftSequence, longestMatchSoFar) { + diff = new LcsDiff(new StringDiffSequence(left), new StringDiffSequence(right), function (leftIndex, longestMatchSoFar) { assert(longestMatchSoFar <= 1); // We never see a match of length > 1 // Continue processing as long as there hasn't been a match made. return longestMatchSoFar < 1; }); - changes = diff.ComputeDiff(true); + changes = diff.ComputeDiff(true).changes; assertAnswer(left, right, changes, 'abcf'); // Cancel after the second match ('d') - diff = new LcsDiff(new StringDiffSequence(left), new StringDiffSequence(right), function (leftIndex, leftSequence, longestMatchSoFar) { + diff = new LcsDiff(new StringDiffSequence(left), new StringDiffSequence(right), function (leftIndex, longestMatchSoFar) { assert(longestMatchSoFar <= 2); // We never see a match of length > 2 // Continue processing as long as there hasn't been a match made. return longestMatchSoFar < 2; }); - changes = diff.ComputeDiff(true); + changes = diff.ComputeDiff(true).changes; assertAnswer(left, right, changes, 'abcdf'); @@ -171,7 +152,7 @@ suite('Diff - Ported from VS', () => { // Cancel *one iteration* after the second match ('d') let hitSecondMatch = false; - diff = new LcsDiff(new StringDiffSequence(left), new StringDiffSequence(right), function (leftIndex, leftSequence, longestMatchSoFar) { + diff = new LcsDiff(new StringDiffSequence(left), new StringDiffSequence(right), function (leftIndex, longestMatchSoFar) { assert(longestMatchSoFar <= 2); // We never see a match of length > 2 let hitYet = hitSecondMatch; @@ -179,20 +160,20 @@ suite('Diff - Ported from VS', () => { // Continue processing as long as there hasn't been a match made. return !hitYet; }); - changes = diff.ComputeDiff(true); + changes = diff.ComputeDiff(true).changes; assertAnswer(left, right, changes, 'abcdf'); // Cancel after the third and final match ('e') - diff = new LcsDiff(new StringDiffSequence(left), new StringDiffSequence(right), function (leftIndex, leftSequence, longestMatchSoFar) { + diff = new LcsDiff(new StringDiffSequence(left), new StringDiffSequence(right), function (leftIndex, longestMatchSoFar) { assert(longestMatchSoFar <= 3); // We never see a match of length > 3 // Continue processing as long as there hasn't been a match made. return longestMatchSoFar < 3; }); - changes = diff.ComputeDiff(true); + changes = diff.ComputeDiff(true).changes; assertAnswer(left, right, changes, 'abcdef'); }); diff --git a/src/vs/base/test/common/lazy.test.ts b/src/vs/base/test/common/lazy.test.ts new file mode 100644 index 0000000000..155b4f2dd9 --- /dev/null +++ b/src/vs/base/test/common/lazy.test.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { Lazy } from 'vs/base/common/lazy'; + +suite('Lazy', () => { + + test('lazy values should only be resolved once', () => { + let counter = 0; + const value = new Lazy(() => ++counter); + + assert.strictEqual(value.hasValue(), false); + assert.strictEqual(value.getValue(), 1); + assert.strictEqual(value.hasValue(), true); + assert.strictEqual(value.getValue(), 1); // make sure we did not evaluate again + }); + + test('lazy values handle error case', () => { + let counter = 0; + const value = new Lazy(() => { throw new Error(`${++counter}`); }); + + assert.strictEqual(value.hasValue(), false); + assert.throws(() => value.getValue(), /\b1\b/); + assert.strictEqual(value.hasValue(), true); + assert.throws(() => value.getValue(), /\b1\b/); + }); + + test('map should not cause lazy values to be re-resolved', () => { + let outer = 0; + let inner = 10; + const outerLazy = new Lazy(() => ++outer); + const innerLazy = outerLazy.map(x => [x, ++inner]); + + assert.strictEqual(outerLazy.hasValue(), false); + assert.strictEqual(innerLazy.hasValue(), false); + + assert.deepEqual(innerLazy.getValue(), [1, 11]); + assert.strictEqual(outerLazy.hasValue(), true); + assert.strictEqual(innerLazy.hasValue(), true); + assert.strictEqual(outerLazy.getValue(), 1); + + // make sure we did not evaluate again + assert.strictEqual(outerLazy.getValue(), 1); + assert.deepEqual(innerLazy.getValue(), [1, 11]); + }); + + test('map should should handle error values', () => { + let outer = 0; + let inner = 10; + const outerLazy = new Lazy(() => { throw new Error(`${++outer}`); }); + const innerLazy = outerLazy.map(x => { throw new Error(`${++inner}`); }); + + assert.strictEqual(outerLazy.hasValue(), false); + assert.strictEqual(innerLazy.hasValue(), false); + + assert.throws(() => innerLazy.getValue(), /\b1\b/); // we should get result from outer + assert.strictEqual(outerLazy.hasValue(), true); + assert.strictEqual(innerLazy.hasValue(), true); + assert.throws(() => outerLazy.getValue(), /\b1\b/); + }); +}); diff --git a/src/vs/base/test/common/resourceTree.test.ts b/src/vs/base/test/common/resourceTree.test.ts index 50293e4217..3247083046 100644 --- a/src/vs/base/test/common/resourceTree.test.ts +++ b/src/vs/base/test/common/resourceTree.test.ts @@ -4,46 +4,70 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ResourceTree, IBranchNode, ILeafNode } from 'vs/base/common/resourceTree'; +import { ResourceTree } from 'vs/base/common/resourceTree'; import { URI } from 'vs/base/common/uri'; suite('ResourceTree', function () { test('ctor', function () { const tree = new ResourceTree(null); - assert(ResourceTree.isBranchNode(tree.root)); - assert.equal(tree.root.size, 0); + assert.equal(tree.root.childrenCount, 0); }); test('simple', function () { const tree = new ResourceTree(null); tree.add(URI.file('/foo/bar.txt'), 'bar contents'); - assert(ResourceTree.isBranchNode(tree.root)); - assert.equal(tree.root.size, 1); + assert.equal(tree.root.childrenCount, 1); - let foo = tree.root.get('foo') as IBranchNode; + let foo = tree.root.get('foo')!; assert(foo); - assert(ResourceTree.isBranchNode(foo)); - assert.equal(foo.size, 1); + assert.equal(foo.childrenCount, 1); - let bar = foo.get('bar.txt') as ILeafNode; + let bar = foo.get('bar.txt')!; assert(bar); - assert(!ResourceTree.isBranchNode(bar)); assert.equal(bar.element, 'bar contents'); tree.add(URI.file('/hello.txt'), 'hello contents'); - assert.equal(tree.root.size, 2); + assert.equal(tree.root.childrenCount, 2); - let hello = tree.root.get('hello.txt') as ILeafNode; + let hello = tree.root.get('hello.txt')!; assert(hello); - assert(!ResourceTree.isBranchNode(hello)); assert.equal(hello.element, 'hello contents'); tree.delete(URI.file('/foo/bar.txt')); - assert.equal(tree.root.size, 1); - hello = tree.root.get('hello.txt') as ILeafNode; + assert.equal(tree.root.childrenCount, 1); + hello = tree.root.get('hello.txt')!; assert(hello); - assert(!ResourceTree.isBranchNode(hello)); assert.equal(hello.element, 'hello contents'); }); + + test('folders with data', function () { + const tree = new ResourceTree(null); + + assert.equal(tree.root.childrenCount, 0); + + tree.add(URI.file('/foo'), 'foo'); + assert.equal(tree.root.childrenCount, 1); + assert.equal(tree.root.get('foo')!.element, 'foo'); + + tree.add(URI.file('/bar'), 'bar'); + assert.equal(tree.root.childrenCount, 2); + assert.equal(tree.root.get('bar')!.element, 'bar'); + + tree.add(URI.file('/foo/file.txt'), 'file'); + assert.equal(tree.root.childrenCount, 2); + assert.equal(tree.root.get('foo')!.element, 'foo'); + assert.equal(tree.root.get('bar')!.element, 'bar'); + assert.equal(tree.root.get('foo')!.get('file.txt')!.element, 'file'); + + tree.delete(URI.file('/foo')); + assert.equal(tree.root.childrenCount, 1); + assert(!tree.root.get('foo')); + assert.equal(tree.root.get('bar')!.element, 'bar'); + + tree.delete(URI.file('/bar')); + assert.equal(tree.root.childrenCount, 0); + assert(!tree.root.get('foo')); + assert(!tree.root.get('bar')); + }); }); diff --git a/src/vs/base/test/common/types.test.ts b/src/vs/base/test/common/types.test.ts index ffc18da589..07e1fc90d0 100644 --- a/src/vs/base/test/common/types.test.ts +++ b/src/vs/base/test/common/types.test.ts @@ -169,6 +169,24 @@ suite('Types', () => { assert(types.isUndefinedOrNull(null)); }); + test('assertIsDefined / assertAreDefined', () => { + assert.throws(() => types.assertIsDefined(undefined)); + assert.throws(() => types.assertIsDefined(null)); + assert.throws(() => types.assertAllDefined(null, undefined)); + assert.throws(() => types.assertAllDefined(true, undefined)); + assert.throws(() => types.assertAllDefined(undefined, false)); + + assert.equal(types.assertIsDefined(true), true); + assert.equal(types.assertIsDefined(false), false); + assert.equal(types.assertIsDefined('Hello'), 'Hello'); + assert.equal(types.assertIsDefined(''), ''); + + const res = types.assertAllDefined(1, true, 'Hello'); + assert.equal(res[0], 1); + assert.equal(res[1], true); + assert.equal(res[2], 'Hello'); + }); + test('validateConstraints', () => { types.validateConstraints([1, 'test', true], [Number, String, Boolean]); types.validateConstraints([1, 'test', true], ['number', 'string', 'boolean']); diff --git a/src/vs/base/test/common/uri.test.ts b/src/vs/base/test/common/uri.test.ts index 0f09e612a8..a43e1cebfa 100644 --- a/src/vs/base/test/common/uri.test.ts +++ b/src/vs/base/test/common/uri.test.ts @@ -439,6 +439,37 @@ suite('URI', () => { assert.equal(uri.path, uri2.path); }); + + test('Links in markdown are broken if url contains encoded parameters #79474', function () { + this.skip(); + let strIn = 'https://myhost.com/Redirect?url=http%3A%2F%2Fwww.bing.com%3Fsearch%3Dtom'; + let uri1 = URI.parse(strIn); + let strOut = uri1.toString(); + let uri2 = URI.parse(strOut); + + assert.equal(uri1.scheme, uri2.scheme); + assert.equal(uri1.authority, uri2.authority); + assert.equal(uri1.path, uri2.path); + assert.equal(uri1.query, uri2.query); + assert.equal(uri1.fragment, uri2.fragment); + assert.equal(strIn, strOut); // fails here!! + }); + + test('Uri#parse can break path-component #45515', function () { + this.skip(); + let strIn = 'https://firebasestorage.googleapis.com/v0/b/brewlangerie.appspot.com/o/products%2FzVNZkudXJyq8bPGTXUxx%2FBetterave-Sesame.jpg?alt=media&token=0b2310c4-3ea6-4207-bbde-9c3710ba0437'; + let uri1 = URI.parse(strIn); + let strOut = uri1.toString(); + let uri2 = URI.parse(strOut); + + assert.equal(uri1.scheme, uri2.scheme); + assert.equal(uri1.authority, uri2.authority); + assert.equal(uri1.path, uri2.path); + assert.equal(uri1.query, uri2.query); + assert.equal(uri1.fragment, uri2.fragment); + assert.equal(strIn, strOut); // fails here!! + }); + test('URI - (de)serialize', function () { const values = [ diff --git a/src/vs/base/test/node/pfs/pfs.test.ts b/src/vs/base/test/node/pfs/pfs.test.ts index a027989e39..ab9bf356b7 100644 --- a/src/vs/base/test/node/pfs/pfs.test.ts +++ b/src/vs/base/test/node/pfs/pfs.test.ts @@ -16,6 +16,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { isWindows, isLinux } from 'vs/base/common/platform'; import { canNormalize } from 'vs/base/common/normalization'; import { VSBuffer } from 'vs/base/common/buffer'; +import { join } from 'path'; const chunkSize = 64 * 1024; const readError = 'Error while reading'; @@ -386,6 +387,31 @@ suite('PFS', () => { } }); + test('readdirWithFileTypes', async () => { + if (canNormalize && typeof process.versions['electron'] !== 'undefined' /* needs electron */) { + const id = uuid.generateUuid(); + const parentDir = path.join(os.tmpdir(), 'vsctests', id); + const testDir = join(parentDir, 'pfs', id); + + const newDir = path.join(testDir, 'öäü'); + await pfs.mkdirp(newDir, 493); + + await pfs.writeFile(join(testDir, 'somefile.txt'), 'contents'); + + assert.ok(fs.existsSync(newDir)); + + const children = await pfs.readdirWithFileTypes(testDir); + + assert.equal(children.some(n => n.name === 'öäü'), true); // Mac always converts to NFD, so + assert.equal(children.some(n => n.isDirectory()), true); + + assert.equal(children.some(n => n.name === 'somefile.txt'), true); + assert.equal(children.some(n => n.isFile()), true); + + await pfs.rimraf(parentDir); + } + }); + test('writeFile (string)', async () => { const smallData = 'Hello World'; const bigData = (new Array(100 * 1024)).join('Large String\n'); diff --git a/src/vs/code/browser/workbench/workbench-dev.html b/src/vs/code/browser/workbench/workbench-dev.html index 910666edf0..7ae960dbc9 100644 --- a/src/vs/code/browser/workbench/workbench-dev.html +++ b/src/vs/code/browser/workbench/workbench-dev.html @@ -7,25 +7,8 @@ - - - - + @@ -50,7 +33,6 @@ 'xterm-addon-search': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-search/lib/xterm-addon-search.js`, 'xterm-addon-web-links': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`, 'semver-umd': `${window.location.origin}/static/remote/web/node_modules/semver-umd/lib/semver-umd.js`, - '@microsoft/applicationinsights-web': `${window.location.origin}/static/remote/web/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js`, } }; diff --git a/src/vs/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html index 97d78ce7f9..dbcb8e490f 100644 --- a/src/vs/code/browser/workbench/workbench.html +++ b/src/vs/code/browser/workbench/workbench.html @@ -7,25 +7,8 @@ - - - - + @@ -37,7 +20,6 @@ - @@ -55,7 +37,6 @@ 'xterm-addon-search': `${window.location.origin}/static/node_modules/xterm-addon-search/lib/xterm-addon-search.js`, 'xterm-addon-web-links': `${window.location.origin}/static/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`, 'semver-umd': `${window.location.origin}/static/node_modules/semver-umd/lib/semver-umd.js`, - '@microsoft/applicationinsights-web': `${window.location.origin}/static/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js`, } }; diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index fb11ab2f96..482ed40369 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -10,6 +10,8 @@ import { streamToBuffer } from 'vs/base/common/buffer'; import { Disposable } from 'vs/base/common/lifecycle'; import { request } from 'vs/base/parts/request/browser/request'; import { isFolderToOpen, isWorkspaceToOpen } from 'vs/platform/windows/common/windows'; +import { isEqual } from 'vs/base/common/resources'; +import { isStandalone } from 'vs/base/browser/browser'; interface ICredential { service: string; @@ -21,7 +23,7 @@ class LocalStorageCredentialsProvider implements ICredentialsProvider { static readonly CREDENTIALS_OPENED_KEY = 'credentials.provider'; - private _credentials!: ICredential[]; + private _credentials: ICredential[] | undefined; private get credentials(): ICredential[] { if (!this._credentials) { try { @@ -102,10 +104,10 @@ class LocalStorageCredentialsProvider implements ICredentialsProvider { class PollingURLCallbackProvider extends Disposable implements IURLCallbackProvider { - static FETCH_INTERVAL = 500; // fetch every 500ms - static FETCH_TIMEOUT = 5 * 60 * 1000; // ...but stop after 5min + static readonly FETCH_INTERVAL = 500; // fetch every 500ms + static readonly FETCH_TIMEOUT = 5 * 60 * 1000; // ...but stop after 5min - static QUERY_KEYS = { + static readonly QUERY_KEYS = { REQUEST_ID: 'vscode-requestId', SCHEME: 'vscode-scheme', AUTHORITY: 'vscode-authority', @@ -200,45 +202,119 @@ class PollingURLCallbackProvider extends Disposable implements IURLCallbackProvi class WorkspaceProvider implements IWorkspaceProvider { - constructor(public readonly workspace: IWorkspace) { } + static QUERY_PARAM_EMPTY_WINDOW = 'ew'; + static QUERY_PARAM_FOLDER = 'folder'; + static QUERY_PARAM_WORKSPACE = 'workspace'; - async open(workspace: IWorkspace, options?: { reuse?: boolean }): Promise { - let targetHref: string | undefined = undefined; + constructor( + public readonly workspace: IWorkspace, + public readonly payload: object + ) { } + + async open(workspace: IWorkspace, options?: { reuse?: boolean, payload?: object }): Promise { + if (options?.reuse && !options.payload && this.isSame(this.workspace, workspace)) { + return; // return early if workspace and environment is not changing and we are reusing window + } // Empty + let targetHref: string | undefined = undefined; if (!workspace) { - targetHref = `${document.location.origin}${document.location.pathname}?ew=true`; + targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_EMPTY_WINDOW}=true`; } // Folder else if (isFolderToOpen(workspace)) { - targetHref = `${document.location.origin}${document.location.pathname}?folder=${workspace.folderUri.path}`; + targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_FOLDER}=${workspace.folderUri.path}`; } // Workspace else if (isWorkspaceToOpen(workspace)) { - targetHref = `${document.location.origin}${document.location.pathname}?workspace=${workspace.workspaceUri.path}`; + targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_WORKSPACE}=${workspace.workspaceUri.path}`; + } + + // Environment + if (options?.payload) { + targetHref += `&payload=${encodeURIComponent(JSON.stringify(options.payload))}`; } if (targetHref) { - if (options && options.reuse) { + if (options?.reuse) { window.location.href = targetHref; } else { - window.open(targetHref); + if (isStandalone) { + window.open(targetHref, '_blank', 'toolbar=no'); // ensures to open another 'standalone' window! + } else { + window.open(targetHref); + } } } } + + private isSame(workspaceA: IWorkspace, workspaceB: IWorkspace): boolean { + if (!workspaceA || !workspaceB) { + return workspaceA === workspaceB; // both empty + } + + if (isFolderToOpen(workspaceA) && isFolderToOpen(workspaceB)) { + return isEqual(workspaceA.folderUri, workspaceB.folderUri); // same workspace + } + + if (isWorkspaceToOpen(workspaceA) && isWorkspaceToOpen(workspaceB)) { + return isEqual(workspaceA.workspaceUri, workspaceB.workspaceUri); // same workspace + } + + return false; + } } -const options: IWorkbenchConstructionOptions & { folderUri?: UriComponents, workspaceUri?: UriComponents } = JSON.parse(document.getElementById('vscode-workbench-web-configuration')!.getAttribute('data-settings')!); -options.workspaceProvider = new WorkspaceProvider(options.folderUri ? { folderUri: URI.revive(options.folderUri) } : options.workspaceUri ? { workspaceUri: URI.revive(options.workspaceUri) } : undefined); -options.urlCallbackProvider = new PollingURLCallbackProvider(); -options.credentialsProvider = new LocalStorageCredentialsProvider(); +(function () { -if (Array.isArray(options.staticExtensions)) { - options.staticExtensions.forEach(extension => { - extension.extensionLocation = URI.revive(extension.extensionLocation); - }); -} + // Find config element in DOM + const configElement = document.getElementById('vscode-workbench-web-configuration'); + const configElementAttribute = configElement ? configElement.getAttribute('data-settings') : undefined; + if (!configElement || !configElementAttribute) { + throw new Error('Missing web configuration element'); + } -create(document.body, options); + const options: IWorkbenchConstructionOptions & { folderUri?: UriComponents, workspaceUri?: UriComponents } = JSON.parse(configElementAttribute); + + // Determine workspace to open + let workspace: IWorkspace; + if (options.folderUri) { + workspace = { folderUri: URI.revive(options.folderUri) }; + } else if (options.workspaceUri) { + workspace = { workspaceUri: URI.revive(options.workspaceUri) }; + } else { + workspace = undefined; + } + + // Find payload + let payload = Object.create(null); + if (document.location.search) { + const query = document.location.search.substring(1); + const vars = query.split('&'); + for (let p of vars) { + const pair = p.split('='); + if (pair.length === 2) { + const [key, value] = pair; + if (key === 'payload') { + payload = JSON.parse(decodeURIComponent(value)); + break; + } + } + } + } + + options.workspaceProvider = new WorkspaceProvider(workspace, payload); + options.urlCallbackProvider = new PollingURLCallbackProvider(); + options.credentialsProvider = new LocalStorageCredentialsProvider(); + + if (Array.isArray(options.staticExtensions)) { + options.staticExtensions.forEach(extension => { + extension.extensionLocation = URI.revive(extension.extensionLocation); + }); + } + + // Finally create workbench + create(document.body, options); +})(); diff --git a/src/vs/code/electron-browser/issue/issueReporter.js b/src/vs/code/electron-browser/issue/issueReporter.js index d3cf774e8d..479eaa2a47 100644 --- a/src/vs/code/electron-browser/issue/issueReporter.js +++ b/src/vs/code/electron-browser/issue/issueReporter.js @@ -10,4 +10,4 @@ const bootstrapWindow = require('../../../../bootstrap-window'); bootstrapWindow.load(['vs/code/electron-browser/issue/issueReporterMain'], function (issueReporter, configuration) { issueReporter.startup(configuration); -}, { forceEnableDeveloperKeybindings: true }); \ No newline at end of file +}, { forceEnableDeveloperKeybindings: true, disallowReloadKeybinding: true }); diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index 5cfb0cf896..ffb9fdac5f 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -34,10 +34,9 @@ import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssue import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage'; import { LoggerChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; import { ILogService, getLogLevel } from 'vs/platform/log/common/log'; -import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; +import { CodiconLabel } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; import { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporterUtil'; import { Button } from 'vs/base/browser/ui/button/button'; -import { withUndefinedAsNull } from 'vs/base/common/types'; import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; import { SpdLogService } from 'vs/platform/log/node/spdlogService'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; @@ -232,7 +231,7 @@ export class IssueReporter extends Disposable { styleTag.innerHTML = content.join('\n'); document.head.appendChild(styleTag); - document.body.style.color = withUndefinedAsNull(styles.color); + document.body.style.color = styles.color || ''; } private handleExtensionData(extensions: IssueReporterExtensionData[]) { @@ -649,8 +648,8 @@ export class IssueReporter extends Disposable { issueState = $('span.issue-state'); const issueIcon = $('span.issue-icon'); - const octicon = new OcticonLabel(issueIcon); - octicon.text = issue.state === 'open' ? '$(issue-opened)' : '$(issue-closed)'; + const codicon = new CodiconLabel(issueIcon); + codicon.text = issue.state === 'open' ? '$(issue-opened)' : '$(issue-closed)'; const issueStateLabel = $('span.issue-state.label'); issueStateLabel.textContent = issue.state === 'open' ? localize('open', "Open") : localize('closed', "Closed"); diff --git a/src/vs/code/electron-browser/issue/media/issueReporter.css b/src/vs/code/electron-browser/issue/media/issueReporter.css index b256e0359a..e0367ee29b 100644 --- a/src/vs/code/electron-browser/issue/media/issueReporter.css +++ b/src/vs/code/electron-browser/issue/media/issueReporter.css @@ -352,7 +352,7 @@ a { text-overflow: ellipsis; } -.issues-container > .issue > .issue-state .octicon { +.issues-container > .issue > .issue-state .codicon { width: 16px; } diff --git a/src/vs/code/electron-browser/processExplorer/media/collapsed.svg b/src/vs/code/electron-browser/processExplorer/media/collapsed.svg old mode 100755 new mode 100644 diff --git a/src/vs/code/electron-browser/processExplorer/media/expanded.svg b/src/vs/code/electron-browser/processExplorer/media/expanded.svg old mode 100755 new mode 100644 diff --git a/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts b/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts index d69a40090c..9dbd503f55 100644 --- a/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts @@ -19,7 +19,6 @@ import { addDisposableListener } from 'vs/base/browser/dom'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; - let mapPidToWindowTitle = new Map(); const DEBUG_FLAGS_PATTERN = /\s--(inspect|debug)(-brk|port)?=(\d+)?/; @@ -374,9 +373,20 @@ function requestProcessList(totalWaitTime: number): void { }, 200); } +function createCloseListener(): void { + // Cmd/Ctrl + w closes process explorer + window.addEventListener('keydown', e => { + const cmdOrCtrlKey = platform.isMacintosh ? e.metaKey : e.ctrlKey; + if (cmdOrCtrlKey && e.keyCode === 87) { + ipcRenderer.send('vscode:closeProcessExplorer'); + } + }); +} + export function startup(data: ProcessExplorerData): void { applyStyles(data.styles); applyZoom(data.zoomLevel); + createCloseListener(); // Map window process pids to titles, annotate process names with this when rendering to distinguish between them ipcRenderer.on('vscode:windowsInfoResponse', (_event: unknown, windows: any[]) => { diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts index 5e856bff2a..3b8981e238 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts @@ -13,7 +13,7 @@ import { IBackupWorkspacesFormat } from 'vs/platform/backup/node/backup'; export class StorageDataCleaner extends Disposable { // Workspace/Folder storage names are MD5 hashes (128bits / 4 due to hex presentation) - private static NON_EMPTY_WORKSPACE_ID_LENGTH = 128 / 4; + private static readonly NON_EMPTY_WORKSPACE_ID_LENGTH = 128 / 4; constructor( @IEnvironmentService private readonly environmentService: IEnvironmentService diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index beb9da5e1b..8958936e2d 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -3,9 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol, powerMonitor, Event as IpcMainEvent, BrowserWindow } from 'electron'; +import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol, powerMonitor, IpcMainEvent, BrowserWindow } from 'electron'; import { IProcessEnvironment, isWindows, isMacintosh } from 'vs/base/common/platform'; -import { WindowsManager } from 'vs/code/electron-main/windows'; +import { WindowsMainService } from 'vs/platform/windows/electron-main/windowsMainService'; import { OpenContext, IWindowOpenable } from 'vs/platform/windows/common/windows'; import { ActiveWindowManager } from 'vs/code/node/activeWindowTracker'; import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; @@ -24,7 +24,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IStateService } from 'vs/platform/state/node/state'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IURLService } from 'vs/platform/url/common/url'; +import { IURLService, IOpenURLOptions } from 'vs/platform/url/common/url'; import { URLHandlerChannelClient, URLHandlerRouter } from 'vs/platform/url/common/urlIpc'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils'; @@ -74,8 +74,7 @@ import { FileService } from 'vs/platform/files/common/fileService'; import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; -import { IElectronService } from 'vs/platform/electron/node/electron'; -import { ElectronMainService } from 'vs/platform/electron/electron-main/electronMainService'; +import { IElectronMainService, ElectronMainService } from 'vs/platform/electron/electron-main/electronMainService'; import { ISharedProcessMainService, SharedProcessMainService } from 'vs/platform/ipc/electron-main/sharedProcessMainService'; import { assign } from 'vs/base/common/objects'; import { IDialogMainService, DialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; @@ -377,7 +376,7 @@ export class CodeApplication extends Disposable { // Create driver if (this.environmentService.driverHandle) { - const server = await serveDriver(electronIpcServer, this.environmentService.driverHandle!, this.environmentService, appInstantiationService); + const server = await serveDriver(electronIpcServer, this.environmentService.driverHandle, this.environmentService, appInstantiationService); this.logService.info('Driver started at:', this.environmentService.driverHandle); this._register(server); @@ -450,7 +449,7 @@ export class CodeApplication extends Disposable { break; } - services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, [machineId, this.userEnv])); + services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, this.userEnv])); services.set(IDialogMainService, new SyncDescriptor(DialogMainService)); services.set(ISharedProcessMainService, new SyncDescriptor(SharedProcessMainService, [sharedProcess])); services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService)); @@ -459,7 +458,7 @@ export class CodeApplication extends Disposable { services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService, [diagnosticsChannel])); services.set(IIssueService, new SyncDescriptor(IssueMainService, [machineId, this.userEnv])); - services.set(IElectronService, new SyncDescriptor(ElectronMainService)); + services.set(IElectronMainService, new SyncDescriptor(ElectronMainService)); services.set(IWorkspacesService, new SyncDescriptor(WorkspacesService)); services.set(IMenubarService, new SyncDescriptor(MenubarMainService)); @@ -546,8 +545,8 @@ export class CodeApplication extends Disposable { const issueChannel = createChannelReceiver(issueService); electronIpcServer.registerChannel('issue', issueChannel); - const electronService = accessor.get(IElectronService); - const electronChannel = createChannelReceiver(electronService); + const electronMainService = accessor.get(IElectronMainService); + const electronChannel = createChannelReceiver(electronMainService); electronIpcServer.registerChannel('electron', electronChannel); sharedProcessClient.then(client => client.registerChannel('electron', electronChannel)); @@ -588,12 +587,14 @@ export class CodeApplication extends Disposable { // Create a URL handler to open file URIs in the active window const environmentService = accessor.get(IEnvironmentService); urlService.registerHandler({ - async handleURL(uri: URI): Promise { + async handleURL(uri: URI, options?: IOpenURLOptions): Promise { // Catch file URLs if (uri.authority === Schemas.file && !!uri.path) { const cli = assign(Object.create(null), environmentService.args); - const urisToOpen = [{ fileUri: uri }]; + + // hey Ben, we need to convert this `code://file` URI into a `file://` URI + const urisToOpen = [{ fileUri: URI.file(uri.fsPath) }]; windowsMainService.open({ context: OpenContext.API, cli, urisToOpen, gotoLineMode: true }); @@ -605,7 +606,7 @@ export class CodeApplication extends Disposable { }); // Create a URL handler which forwards to the last active window - const activeWindowManager = new ActiveWindowManager(electronService); + const activeWindowManager = new ActiveWindowManager(electronMainService); const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id)); const urlHandlerRouter = new URLHandlerRouter(activeWindowRouter); const urlHandlerChannel = electronIpcServer.getChannel('urlHandler', urlHandlerRouter); @@ -615,7 +616,7 @@ export class CodeApplication extends Disposable { // if there is none if (isMacintosh) { urlService.registerHandler({ - async handleURL(uri: URI): Promise { + async handleURL(uri: URI, options?: IOpenURLOptions): Promise { if (windowsMainService.getWindowCount() === 0) { const cli = { ...environmentService.args }; const [window] = windowsMainService.open({ context: OpenContext.API, cli, forceEmpty: true, gotoLineMode: true }); @@ -662,7 +663,7 @@ export class CodeApplication extends Disposable { } // mac: open-file event received on startup - if (macOpenFiles && macOpenFiles.length && !hasCliArgs && !hasFolderURIs && !hasFileURIs) { + if (macOpenFiles.length && !hasCliArgs && !hasFolderURIs && !hasFileURIs) { return windowsMainService.open({ context: OpenContext.DOCK, cli: args, diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index ab8ad1c5a2..cc50acfc4c 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -6,7 +6,7 @@ import 'vs/platform/update/common/update.config.contribution'; import { app, dialog } from 'electron'; import { assign } from 'vs/base/common/objects'; -import * as platform from 'vs/base/common/platform'; +import { isWindows, IProcessEnvironment, isMacintosh } from 'vs/base/common/platform'; import product from 'vs/platform/product/common/product'; import { parseMainProcessArgv } from 'vs/platform/environment/node/argvHelper'; import { addArg, createWaitMarkerFile } from 'vs/platform/environment/node/argv'; @@ -132,7 +132,7 @@ class CodeMain { } } - private createServices(args: ParsedArgs, bufferLogService: BufferLogService): [IInstantiationService, typeof process.env] { + private createServices(args: ParsedArgs, bufferLogService: BufferLogService): [IInstantiationService, IProcessEnvironment] { const services = new ServiceCollection(); const environmentService = new EnvironmentService(args, process.execPath); @@ -174,18 +174,17 @@ class CodeMain { 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'] + private patchEnvironment(environmentService: IEnvironmentService): IProcessEnvironment { + const instanceEnvironment: IProcessEnvironment = { + VSCODE_IPC_HOOK: environmentService.mainIPCHandle }; - if (process.env['VSCODE_PORTABLE']) { - instanceEnvironment['VSCODE_PORTABLE'] = process.env['VSCODE_PORTABLE']; - } + ['VSCODE_NLS_CONFIG', 'VSCODE_LOGS', 'VSCODE_PORTABLE', 'ADS_LOGS'].forEach(key => { // {{SQL CARBON EDIT}} add ads logs + const value = process.env[key]; + if (typeof value === 'string') { + instanceEnvironment[key] = value; + } + }); assign(process.env, instanceEnvironment); @@ -215,7 +214,7 @@ class CodeMain { } // Since we are the second instance, we do not want to show the dock - if (platform.isMacintosh) { + if (isMacintosh) { app.dock.hide(); } @@ -226,7 +225,7 @@ class CodeMain { } catch (error) { // Handle unexpected connection errors by showing a dialog to the user - if (!retry || platform.isWindows || error.code !== 'ECONNREFUSED') { + if (!retry || isWindows || error.code !== 'ECONNREFUSED') { if (error.code === 'EPERM') { this.showStartupWarningDialog( localize('secondInstanceAdmin', "A second instance of {0} is already running as administrator.", product.nameShort), @@ -292,16 +291,16 @@ class CodeMain { } // Windows: allow to set foreground - if (platform.isWindows) { + if (isWindows) { await this.windowsAllowSetForegroundWindow(launchService, logService); } // Send environment over... logService.trace('Sending env to running instance...'); - await launchService.start(environmentService.args, process.env as platform.IProcessEnvironment); + await launchService.start(environmentService.args, process.env as IProcessEnvironment); // Cleanup - await client.dispose(); + client.dispose(); // Now that we started, make sure the warning dialog is prevented if (startupWarningDialogHandle) { @@ -319,7 +318,7 @@ class CodeMain { } // dock might be hidden at this case due to a retry - if (platform.isMacintosh) { + if (isMacintosh) { app.dock.show(); } @@ -361,7 +360,7 @@ class CodeMain { } private async windowsAllowSetForegroundWindow(launchService: ILaunchMainService, logService: ILogService): Promise { - if (platform.isWindows) { + if (isWindows) { const processId = await launchService.getMainProcessId(); logService.trace('Sending some foreground love to the running instance:', processId); diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 4d82f883a3..05b741cae2 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -6,6 +6,7 @@ import * as path from 'vs/base/common/path'; import * as objects from 'vs/base/common/objects'; import * as nls from 'vs/nls'; +import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment } from 'electron'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; @@ -13,7 +14,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import product from 'vs/platform/product/common/product'; -import { IWindowSettings, MenuBarVisibility, IWindowConfiguration, ReadyState, getTitleBarStyle } from 'vs/platform/windows/common/windows'; +import { IWindowSettings, MenuBarVisibility, IWindowConfiguration, ReadyState, getTitleBarStyle, getMenuBarVisibility } from 'vs/platform/windows/common/windows'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { ICodeWindow, IWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; @@ -27,6 +28,9 @@ import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainServ import { endsWith } from 'vs/base/common/strings'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IFileService } from 'vs/platform/files/common/files'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; +import { mnemonicButtonLabel } from 'vs/base/common/labels'; const RUN_TEXTMATE_IN_WORKER = false; @@ -48,26 +52,37 @@ interface ITouchBarSegment extends SegmentedControlSegment { id: string; } +const enum WindowError { + UNRESPONSIVE = 1, + CRASHED = 2 +} + export class CodeWindow extends Disposable implements ICodeWindow { - private static readonly MIN_WIDTH = 200; - private static readonly MIN_HEIGHT = 120; + private static readonly MIN_WIDTH = 600; + private static readonly MIN_HEIGHT = 600; private static readonly MAX_URL_LENGTH = 2 * 1024 * 1024; // https://cs.chromium.org/chromium/src/url/url_constants.cc?l=32 - private hiddenTitleBarStyle: boolean; - private showTimeoutHandle: NodeJS.Timeout; - private _id: number; - private _win: BrowserWindow; + private readonly _onClose = this._register(new Emitter()); + readonly onClose: CommonEvent = this._onClose.event; + + private readonly _onDestroy = this._register(new Emitter()); + readonly onDestroy: CommonEvent = this._onDestroy.event; + + private readonly _onLoad = this._register(new Emitter()); + readonly onLoad: CommonEvent = this._onLoad.event; + + private hiddenTitleBarStyle: boolean | undefined; + private showTimeoutHandle: NodeJS.Timeout | undefined; private _lastFocusTime: number; private _readyState: ReadyState; private windowState: IWindowState; - private currentMenuBarVisibility: MenuBarVisibility; - private representedFilename: string; + private currentMenuBarVisibility: MenuBarVisibility | undefined; + private representedFilename: string | undefined; private readonly whenReadyCallbacks: { (window: ICodeWindow): void }[]; - private currentConfig: IWindowConfiguration; private pendingLoadConfig?: IWindowConfiguration; private marketplaceHeadersPromise: Promise; @@ -83,6 +98,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { @IThemeMainService private readonly themeMainService: IThemeMainService, @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, @IBackupMainService private readonly backupMainService: IBackupMainService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IDialogMainService private readonly dialogMainService: IDialogMainService ) { super(); @@ -91,8 +108,111 @@ export class CodeWindow extends Disposable implements ICodeWindow { this._readyState = ReadyState.NONE; this.whenReadyCallbacks = []; - // create browser window - this.createBrowserWindow(config); + //#region create browser window + { + // Load window state + const [state, hasMultipleDisplays] = this.restoreWindowState(config.state); + this.windowState = state; + + // in case we are maximized or fullscreen, only show later after the call to maximize/fullscreen (see below) + const isFullscreenOrMaximized = (this.windowState.mode === WindowMode.Maximized || this.windowState.mode === WindowMode.Fullscreen); + + const options: BrowserWindowConstructorOptions = { + width: this.windowState.width, + height: this.windowState.height, + x: this.windowState.x, + y: this.windowState.y, + backgroundColor: this.themeMainService.getBackgroundColor(), + minWidth: CodeWindow.MIN_WIDTH, + minHeight: CodeWindow.MIN_HEIGHT, + show: !isFullscreenOrMaximized, + title: product.nameLong, + webPreferences: { + // By default if Code is in the background, intervals and timeouts get throttled, so we + // want to enforce that Code stays in the foreground. This triggers a disable_hidden_ + // flag that Electron provides via patch: + // https://github.com/electron/libchromiumcontent/blob/master/patches/common/chromium/disable_hidden.patch + backgroundThrottling: false, + nodeIntegration: true, + nodeIntegrationInWorker: RUN_TEXTMATE_IN_WORKER, + webviewTag: true + } + }; + + if (isLinux) { + options.icon = path.join(this.environmentService.appRoot, 'resources/linux/code.png'); // Windows and Mac are better off using the embedded icon(s) + } + + const windowConfig = this.configurationService.getValue('window'); + + if (isMacintosh && !this.useNativeFullScreen()) { + options.fullscreenable = false; // enables simple fullscreen mode + } + + if (isMacintosh) { + options.acceptFirstMouse = true; // enabled by default + + if (windowConfig?.clickThroughInactive === false) { + options.acceptFirstMouse = false; + } + } + + const useNativeTabs = isMacintosh && windowConfig?.nativeTabs === true; + if (useNativeTabs) { + options.tabbingIdentifier = product.nameShort; // this opts in to sierra tabs + } + + const useCustomTitleStyle = getTitleBarStyle(this.configurationService, this.environmentService, !!config.extensionDevelopmentPath) === 'custom'; + if (useCustomTitleStyle) { + options.titleBarStyle = 'hidden'; + this.hiddenTitleBarStyle = true; + if (!isMacintosh) { + options.frame = false; + } + } + + // Create the browser window. + this._win = new BrowserWindow(options); + this._id = this._win.id; + + if (isMacintosh && useCustomTitleStyle) { + this._win.setSheetOffset(22); // offset dialogs by the height of the custom title bar if we have any + } + + // TODO@Ben (Electron 4 regression): when running on multiple displays where the target display + // to open the window has a larger resolution than the primary display, the window will not size + // correctly unless we set the bounds again (https://github.com/microsoft/vscode/issues/74872) + // + // However, when running with native tabs with multiple windows we cannot use this workaround + // because there is a potential that the new window will be added as native tab instead of being + // a window on its own. In that case calling setBounds() would cause https://github.com/microsoft/vscode/issues/75830 + if (isMacintosh && hasMultipleDisplays && (!useNativeTabs || BrowserWindow.getAllWindows().length === 1)) { + if ([this.windowState.width, this.windowState.height, this.windowState.x, this.windowState.y].every(value => typeof value === 'number')) { + const ensuredWindowState = this.windowState as Required; + this._win.setBounds({ + width: ensuredWindowState.width, + height: ensuredWindowState.height, + x: ensuredWindowState.x, + y: ensuredWindowState.y + }); + } + } + + if (isFullscreenOrMaximized) { + this._win.maximize(); + + if (this.windowState.mode === WindowMode.Fullscreen) { + this.setFullScreen(true); + } + + if (!this._win.isVisible()) { + this._win.show(); // to reduce flicker from the default window size to maximize, we only show after maximize + } + } + + this._lastFocusTime = Date.now(); // since we show directly, we need to set the last focus time too + } + //#endregion // respect configured menu bar visibility this.onConfigurationUpdated(); @@ -101,139 +221,26 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.createTouchBar(); // Request handling - this.handleMarketplaceRequests(); + this.marketplaceHeadersPromise = resolveMarketplaceHeaders(product.version, this.environmentService, this.fileService); // Eventing this.registerListeners(); } - private createBrowserWindow(config: IWindowCreationOptions): void { + private currentConfig: IWindowConfiguration | undefined; + get config(): IWindowConfiguration | undefined { return this.currentConfig; } - // Load window state - const [state, hasMultipleDisplays] = this.restoreWindowState(config.state); - this.windowState = state; + private _id: number; + get id(): number { return this._id; } - // in case we are maximized or fullscreen, only show later after the call to maximize/fullscreen (see below) - const isFullscreenOrMaximized = (this.windowState.mode === WindowMode.Maximized || this.windowState.mode === WindowMode.Fullscreen); + private _win: BrowserWindow; + get win(): BrowserWindow { return this._win; } - const options: BrowserWindowConstructorOptions = { - width: this.windowState.width, - height: this.windowState.height, - x: this.windowState.x, - y: this.windowState.y, - backgroundColor: this.themeMainService.getBackgroundColor(), - minWidth: CodeWindow.MIN_WIDTH, - minHeight: CodeWindow.MIN_HEIGHT, - show: !isFullscreenOrMaximized, - title: product.nameLong, - webPreferences: { - // By default if Code is in the background, intervals and timeouts get throttled, so we - // want to enforce that Code stays in the foreground. This triggers a disable_hidden_ - // flag that Electron provides via patch: - // https://github.com/electron/libchromiumcontent/blob/master/patches/common/chromium/disable_hidden.patch - backgroundThrottling: false, - nodeIntegration: true, - nodeIntegrationInWorker: RUN_TEXTMATE_IN_WORKER, - webviewTag: true - } - }; + get hasHiddenTitleBarStyle(): boolean { return !!this.hiddenTitleBarStyle; } - if (isLinux) { - options.icon = path.join(this.environmentService.appRoot, 'resources/linux/code.png'); // Windows and Mac are better off using the embedded icon(s) - } + get isExtensionDevelopmentHost(): boolean { return !!(this.config && this.config.extensionDevelopmentPath); } - const windowConfig = this.configurationService.getValue('window'); - - if (isMacintosh && !this.useNativeFullScreen()) { - options.fullscreenable = false; // enables simple fullscreen mode - } - - if (isMacintosh) { - options.acceptFirstMouse = true; // enabled by default - - if (windowConfig && windowConfig.clickThroughInactive === false) { - options.acceptFirstMouse = false; - } - } - - const useNativeTabs = isMacintosh && windowConfig && windowConfig.nativeTabs === true; - if (useNativeTabs) { - options.tabbingIdentifier = product.nameShort; // this opts in to sierra tabs - } - - const useCustomTitleStyle = getTitleBarStyle(this.configurationService, this.environmentService, !!config.extensionDevelopmentPath) === 'custom'; - if (useCustomTitleStyle) { - options.titleBarStyle = 'hidden'; - this.hiddenTitleBarStyle = true; - if (!isMacintosh) { - options.frame = false; - } - } - - // Create the browser window. - this._win = new BrowserWindow(options); - this._id = this._win.id; - - if (isMacintosh && useCustomTitleStyle) { - this._win.setSheetOffset(22); // offset dialogs by the height of the custom title bar if we have any - } - - // TODO@Ben (Electron 4 regression): when running on multiple displays where the target display - // to open the window has a larger resolution than the primary display, the window will not size - // correctly unless we set the bounds again (https://github.com/microsoft/vscode/issues/74872) - // - // However, when running with native tabs with multiple windows we cannot use this workaround - // because there is a potential that the new window will be added as native tab instead of being - // a window on its own. In that case calling setBounds() would cause https://github.com/microsoft/vscode/issues/75830 - if (isMacintosh && hasMultipleDisplays && (!useNativeTabs || BrowserWindow.getAllWindows().length === 1)) { - if ([this.windowState.width, this.windowState.height, this.windowState.x, this.windowState.y].every(value => typeof value === 'number')) { - this._win.setBounds({ - width: this.windowState.width!, - height: this.windowState.height!, - x: this.windowState.x!, - y: this.windowState.y! - }); - } - } - - if (isFullscreenOrMaximized) { - this._win.maximize(); - - if (this.windowState.mode === WindowMode.Fullscreen) { - this.setFullScreen(true); - } - - if (!this._win.isVisible()) { - this._win.show(); // to reduce flicker from the default window size to maximize, we only show after maximize - } - } - - this._lastFocusTime = Date.now(); // since we show directly, we need to set the last focus time too - } - - hasHiddenTitleBarStyle(): boolean { - return this.hiddenTitleBarStyle; - } - - get isExtensionDevelopmentHost(): boolean { - return !!this.config.extensionDevelopmentPath; - } - - get isExtensionTestHost(): boolean { - return !!this.config.extensionTestsPath; - } - - get config(): IWindowConfiguration { - return this.currentConfig; - } - - get id(): number { - return this._id; - } - - get win(): BrowserWindow { - return this._win; - } + get isExtensionTestHost(): boolean { return !!(this.config && this.config.extensionTestsPath); } setRepresentedFilename(filename: string): void { if (isMacintosh) { @@ -243,7 +250,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } } - getRepresentedFilename(): string { + getRepresentedFilename(): string | undefined { if (isMacintosh) { return this.win.getRepresentedFilename(); } @@ -263,25 +270,15 @@ export class CodeWindow extends Disposable implements ICodeWindow { this._win.focus(); } - get lastFocusTime(): number { - return this._lastFocusTime; - } + get lastFocusTime(): number { return this._lastFocusTime; } - get backupPath(): string | undefined { - return this.currentConfig ? this.currentConfig.backupPath : undefined; - } + get backupPath(): string | undefined { return this.currentConfig ? this.currentConfig.backupPath : undefined; } - get openedWorkspace(): IWorkspaceIdentifier | undefined { - return this.currentConfig ? this.currentConfig.workspace : undefined; - } + get openedWorkspace(): IWorkspaceIdentifier | undefined { return this.currentConfig ? this.currentConfig.workspace : undefined; } - get openedFolderUri(): URI | undefined { - return this.currentConfig ? this.currentConfig.folderUri : undefined; - } + get openedFolderUri(): URI | undefined { return this.currentConfig ? this.currentConfig.folderUri : undefined; } - get remoteAuthority(): string | undefined { - return this.currentConfig ? this.currentConfig.remoteAuthority : undefined; - } + get remoteAuthority(): string | undefined { return this.currentConfig ? this.currentConfig.remoteAuthority : undefined; } setReady(): void { this._readyState = ReadyState.READY; @@ -307,26 +304,34 @@ export class CodeWindow extends Disposable implements ICodeWindow { return this._readyState === ReadyState.READY; } - private handleMarketplaceRequests(): void { + get whenClosedOrLoaded(): Promise { + return new Promise(resolve => { - // Resolve marketplace headers - this.marketplaceHeadersPromise = resolveMarketplaceHeaders(product.version, this.environmentService, this.fileService); + function handle() { + closeListener.dispose(); + loadListener.dispose(); - // Inject headers when requests are incoming - const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*']; - this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) => { - this.marketplaceHeadersPromise.then(headers => { - const requestHeaders = objects.assign(details.requestHeaders, headers) as { [key: string]: string | undefined }; - if (!this.configurationService.getValue('extensions.disableExperimentalAzureSearch')) { - requestHeaders['Cookie'] = `${requestHeaders['Cookie'] ? requestHeaders['Cookie'] + ';' : ''}EnableExternalSearchForVSCode=true`; - } - cb({ cancel: false, requestHeaders }); - }); + resolve(); + } + + const closeListener = this.onClose(() => handle()); + const loadListener = this.onLoad(() => handle()); }); } private registerListeners(): void { + // Crashes & Unrsponsive + this._win.webContents.on('crashed', () => this.onWindowError(WindowError.CRASHED)); + this._win.on('unresponsive', () => this.onWindowError(WindowError.UNRESPONSIVE)); + + // Window close + this._win.on('closed', () => { + this._onClose.fire(); + + this.dispose(); + }); + // Prevent loading of svgs this._win.webContents.session.webRequest.onBeforeRequest(null!, (details, callback) => { if (details.url.indexOf('.svg') > 0) { @@ -376,7 +381,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { return; // disposed } - if (!this.useNativeFullScreen() && this.isFullScreen()) { + if (!this.useNativeFullScreen() && this.isFullScreen) { this.setFullScreen(false); this.setFullScreen(true); } @@ -430,13 +435,97 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Handle Workspace events this._register(this.workspacesMainService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e))); + + // Inject headers when requests are incoming + const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*']; + this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) => { + this.marketplaceHeadersPromise.then(headers => { + const requestHeaders = objects.assign(details.requestHeaders, headers) as { [key: string]: string | undefined }; + if (!this.configurationService.getValue('extensions.disableExperimentalAzureSearch')) { + requestHeaders['Cookie'] = `${requestHeaders['Cookie'] ? requestHeaders['Cookie'] + ';' : ''}EnableExternalSearchForVSCode=true`; + } + cb({ cancel: false, requestHeaders }); + }); + }); + } + + private onWindowError(error: WindowError): void { + this.logService.error(error === WindowError.CRASHED ? '[VS Code]: render process crashed!' : '[VS Code]: detected unresponsive'); + + type WindowErrorClassification = { + type: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true }; + }; + type WindowErrorEvent = { + type: WindowError; + }; + this.telemetryService.publicLog2('windowerror', { type: error }); + + // Unresponsive + if (error === WindowError.UNRESPONSIVE) { + if (this.isExtensionDevelopmentHost || this.isExtensionTestHost || (this._win && this._win.webContents && this._win.webContents.isDevToolsOpened())) { + // TODO@Ben Workaround for https://github.com/Microsoft/vscode/issues/56994 + // In certain cases the window can report unresponsiveness because a breakpoint was hit + // and the process is stopped executing. The most typical cases are: + // - devtools are opened and debugging happens + // - window is an extensions development host that is being debugged + // - window is an extension test development host that is being debugged + return; + } + + // Show Dialog + this.dialogMainService.showMessageBox({ + title: product.nameLong, + type: 'warning', + buttons: [mnemonicButtonLabel(nls.localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(nls.localize({ key: 'wait', comment: ['&& denotes a mnemonic'] }, "&&Keep Waiting")), mnemonicButtonLabel(nls.localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))], + message: nls.localize('appStalled', "The window is no longer responding"), + detail: nls.localize('appStalledDetail', "You can reopen or close the window or keep waiting."), + noLink: true + }, this._win).then(result => { + if (!this._win) { + return; // Return early if the window has been going down already + } + + if (result.response === 0) { + this.reload(); + } else if (result.response === 2) { + this.destroyWindow(); + } + }); + } + + // Crashed + else { + this.dialogMainService.showMessageBox({ + title: product.nameLong, + type: 'warning', + buttons: [mnemonicButtonLabel(nls.localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(nls.localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))], + message: nls.localize('appCrashed', "The window has crashed"), + detail: nls.localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."), + noLink: true + }, this._win).then(result => { + if (!this._win) { + return; // Return early if the window has been going down already + } + + if (result.response === 0) { + this.reload(); + } else if (result.response === 1) { + this.destroyWindow(); + } + }); + } + } + + private destroyWindow(): void { + this._onDestroy.fire(); // 'close' event will not be fired on destroy(), so signal crash via explicit event + this._win.destroy(); // make sure to destroy the window as it has crashed } private onUntitledWorkspaceDeleted(workspace: IWorkspaceIdentifier): void { // Make sure to update our workspace config if we detect that it // was deleted - if (this.openedWorkspace && this.openedWorkspace.id === workspace.id) { + if (this.openedWorkspace && this.openedWorkspace.id === workspace.id && this.currentConfig) { this.currentConfig.workspace = undefined; } } @@ -510,6 +599,9 @@ export class CodeWindow extends Disposable implements ICodeWindow { } }, 10000); } + + // Event + this._onLoad.fire(); } reload(configurationIn?: IWindowConfiguration, cli?: ParsedArgs): void { @@ -547,25 +639,25 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Set zoomlevel const windowConfig = this.configurationService.getValue('window'); - const zoomLevel = windowConfig && windowConfig.zoomLevel; + const zoomLevel = windowConfig?.zoomLevel; if (typeof zoomLevel === 'number') { windowConfiguration.zoomLevel = zoomLevel; } // Set fullscreen state - windowConfiguration.fullscreen = this.isFullScreen(); + windowConfiguration.fullscreen = this.isFullScreen; // Set Accessibility Config let autoDetectHighContrast = true; - if (windowConfig && windowConfig.autoDetectHighContrast === false) { + if (windowConfig?.autoDetectHighContrast === false) { autoDetectHighContrast = false; } windowConfiguration.highContrast = isWindows && autoDetectHighContrast && systemPreferences.isInvertedColorScheme(); - windowConfiguration.accessibilitySupport = app.isAccessibilitySupportEnabled(); + windowConfiguration.accessibilitySupport = app.accessibilitySupportEnabled; // Title style related windowConfiguration.maximized = this._win.isMaximized(); - windowConfiguration.frameless = this.hasHiddenTitleBarStyle() && !isMacintosh; + windowConfiguration.frameless = this.hasHiddenTitleBarStyle && !isMacintosh; // Dump Perf Counters windowConfiguration.perfEntries = perf.exportEntries(); @@ -611,7 +703,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } // fullscreen gets special treatment - if (this.isFullScreen()) { + if (this.isFullScreen) { const display = screen.getDisplayMatching(this.getBounds()); const defaultState = defaultWindowState(); @@ -733,7 +825,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Multi Montior (fullscreen): try to find the previously used display if (state.display && state.mode === WindowMode.Fullscreen) { const display = displays.filter(d => d.id === state.display)[0]; - if (display && display.bounds && typeof display.bounds.x === 'number' && typeof display.bounds.y === 'number') { + if (display && typeof display.bounds?.x === 'number' && typeof display.bounds?.y === 'number') { const defaults = defaultWindowState(WindowMode.Fullscreen); // make sure we have good values when the user restores the window defaults.x = display.bounds.x; // carefull to use displays x/y position so that the window ends up on the correct monitor defaults.y = display.bounds.y; @@ -786,7 +878,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } toggleFullScreen(): void { - this.setFullScreen(!this.isFullScreen()); + this.setFullScreen(!this.isFullScreen); } private setFullScreen(fullscreen: boolean): void { @@ -802,12 +894,12 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.sendWhenReady(fullscreen ? 'vscode:enterFullScreen' : 'vscode:leaveFullScreen'); // Respect configured menu bar visibility or default to toggle if not set - this.setMenuBarVisibility(this.currentMenuBarVisibility, false); + if (this.currentMenuBarVisibility) { + this.setMenuBarVisibility(this.currentMenuBarVisibility, false); + } } - isFullScreen(): boolean { - return this._win.isFullScreen() || this._win.isSimpleFullScreen(); - } + get isFullScreen(): boolean { return this._win.isFullScreen() || this._win.isSimpleFullScreen(); } private setNativeFullScreen(fullscreen: boolean): void { if (this._win.isSimpleFullScreen()) { @@ -844,12 +936,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } private getMenuBarVisibility(): MenuBarVisibility { - const windowConfig = this.configurationService.getValue('window'); - if (!windowConfig || !windowConfig.menuBarVisibility) { - return 'default'; - } - - let menuBarVisibility = windowConfig.menuBarVisibility; + let menuBarVisibility = getMenuBarVisibility(this.configurationService, this.environmentService, !!this.config?.extensionDevelopmentPath); if (['visible', 'toggle', 'hidden'].indexOf(menuBarVisibility) < 0) { menuBarVisibility = 'default'; } @@ -883,7 +970,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } private doSetMenuBarVisibility(visibility: MenuBarVisibility): void { - const isFullscreen = this.isFullScreen(); + const isFullscreen = this.isFullScreen; switch (visibility) { case ('default'): diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 1a29315cf3..0ba64781a4 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -14,7 +14,7 @@ import * as paths from 'vs/base/common/path'; import { whenDeleted, writeFileSync } from 'vs/base/node/pfs'; import { findFreePort, randomPort } from 'vs/base/node/ports'; import { resolveTerminalEncoding } from 'vs/base/node/encoding'; -import { isWindows } from 'vs/base/common/platform'; +import { isWindows, isLinux } from 'vs/base/common/platform'; import { ProfilingSession, Target } from 'v8-inspect-profiler'; import { isString } from 'vs/base/common/types'; @@ -360,6 +360,10 @@ export async function main(argv: string[]): Promise { options['stdio'] = 'ignore'; } + if (isLinux) { + addArg(argv, '--no-sandbox'); // Electron 6 introduces a chrome-sandbox that requires root to run. This can fail. Disable sandbox via --no-sandbox + } + const child = spawn(process.execPath, argv.slice(2), options); if (args.wait && waitMarkerFilePath) { diff --git a/src/vs/code/node/paths.ts b/src/vs/code/node/paths.ts index 414da1f06a..d308666733 100644 --- a/src/vs/code/node/paths.ts +++ b/src/vs/code/node/paths.ts @@ -19,12 +19,14 @@ export function validatePaths(args: ParsedArgs): ParsedArgs { args._ = []; } - // Normalize paths and watch out for goto line mode - const paths = doValidatePaths(args._, args.goto); + if (!args['remote']) { + // Normalize paths and watch out for goto line mode + const paths = doValidatePaths(args._, args.goto); + args._ = paths; + } // Update environment - args._ = paths; - args.diff = args.diff && paths.length === 2; + args.diff = args.diff && args._.length === 2; return args; } @@ -135,4 +137,4 @@ function toPath(p: IPathWithLineAndColumn): string { } return segments.join(':'); -} \ No newline at end of file +} diff --git a/src/vs/code/test/electron-main/windowsStateStorage.test.ts b/src/vs/code/test/electron-main/windowsStateStorage.test.ts index 76ad487aa1..502922a989 100644 --- a/src/vs/code/test/electron-main/windowsStateStorage.test.ts +++ b/src/vs/code/test/electron-main/windowsStateStorage.test.ts @@ -6,11 +6,11 @@ import * as assert from 'assert'; import * as os from 'os'; import * as path from 'vs/base/common/path'; -import { restoreWindowsState, getWindowsStateStoreData } from 'vs/code/electron-main/windowsStateStorage'; +import { restoreWindowsState, getWindowsStateStoreData } from 'vs/platform/windows/electron-main/windowsStateStorage'; import { IWindowState as IWindowUIState, WindowMode } from 'vs/platform/windows/electron-main/windows'; import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; -import { IWindowsState, IWindowState } from 'vs/code/electron-main/windows'; +import { IWindowsState, IWindowState } from 'vs/platform/windows/electron-main/windowsMainService'; function getUIState(): IWindowUIState { return { diff --git a/src/vs/editor/browser/config/charWidthReader.ts b/src/vs/editor/browser/config/charWidthReader.ts index 5e66e7c887..49900650f9 100644 --- a/src/vs/editor/browser/config/charWidthReader.ts +++ b/src/vs/editor/browser/config/charWidthReader.ts @@ -71,6 +71,7 @@ class DomCharWidthReader { regularDomNode.style.fontFamily = this._bareFontInfo.getMassagedFontFamily(); regularDomNode.style.fontWeight = this._bareFontInfo.fontWeight; regularDomNode.style.fontSize = this._bareFontInfo.fontSize + 'px'; + regularDomNode.style.fontFeatureSettings = this._bareFontInfo.fontFeatureSettings; regularDomNode.style.lineHeight = this._bareFontInfo.lineHeight + 'px'; regularDomNode.style.letterSpacing = this._bareFontInfo.letterSpacing + 'px'; container.appendChild(regularDomNode); @@ -79,6 +80,7 @@ class DomCharWidthReader { boldDomNode.style.fontFamily = this._bareFontInfo.getMassagedFontFamily(); boldDomNode.style.fontWeight = 'bold'; boldDomNode.style.fontSize = this._bareFontInfo.fontSize + 'px'; + boldDomNode.style.fontFeatureSettings = this._bareFontInfo.fontFeatureSettings; boldDomNode.style.lineHeight = this._bareFontInfo.lineHeight + 'px'; boldDomNode.style.letterSpacing = this._bareFontInfo.letterSpacing + 'px'; container.appendChild(boldDomNode); @@ -87,6 +89,7 @@ class DomCharWidthReader { italicDomNode.style.fontFamily = this._bareFontInfo.getMassagedFontFamily(); italicDomNode.style.fontWeight = this._bareFontInfo.fontWeight; italicDomNode.style.fontSize = this._bareFontInfo.fontSize + 'px'; + italicDomNode.style.fontFeatureSettings = this._bareFontInfo.fontFeatureSettings; italicDomNode.style.lineHeight = this._bareFontInfo.lineHeight + 'px'; italicDomNode.style.letterSpacing = this._bareFontInfo.letterSpacing + 'px'; italicDomNode.style.fontStyle = 'italic'; diff --git a/src/vs/editor/browser/config/configuration.ts b/src/vs/editor/browser/config/configuration.ts index 24dbe9e974..8aa15fa6b9 100644 --- a/src/vs/editor/browser/config/configuration.ts +++ b/src/vs/editor/browser/config/configuration.ts @@ -11,7 +11,7 @@ import * as platform from 'vs/base/common/platform'; import { CharWidthRequest, CharWidthRequestType, readCharWidths } from 'vs/editor/browser/config/charWidthReader'; import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver'; import { CommonEditorConfiguration, IEnvConfiguration } from 'vs/editor/common/config/commonEditorConfig'; -import { EditorOption, IEditorConstructionOptions } from 'vs/editor/common/config/editorOptions'; +import { EditorOption, IEditorConstructionOptions, EditorFontLigatures } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; import { IDimension } from 'vs/editor/common/editorCommon'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; @@ -79,6 +79,7 @@ export interface ISerializedFontInfo { readonly fontFamily: string; readonly fontWeight: string; readonly fontSize: number; + fontFeatureSettings: string; readonly lineHeight: number; readonly letterSpacing: number; readonly isMonospace: boolean; @@ -151,11 +152,14 @@ class CSSBasedConfiguration extends Disposable { return this._cache.getValues().filter(item => item.isTrusted); } - public restoreFontInfo(savedFontInfo: ISerializedFontInfo[]): void { + public restoreFontInfo(savedFontInfos: ISerializedFontInfo[]): void { // Take all the saved font info and insert them in the cache without the trusted flag. // The reason for this is that a font might have been installed on the OS in the meantime. - for (let i = 0, len = savedFontInfo.length; i < len; i++) { - const fontInfo = new FontInfo(savedFontInfo[i], false); + for (let i = 0, len = savedFontInfos.length; i < len; i++) { + const savedFontInfo = savedFontInfos[i]; + // compatibility with older versions of VS Code which did not store this... + savedFontInfo.fontFeatureSettings = savedFontInfo.fontFeatureSettings || EditorFontLigatures.OFF; + const fontInfo = new FontInfo(savedFontInfo, false); this._writeToCache(fontInfo, fontInfo); } } @@ -171,6 +175,7 @@ class CSSBasedConfiguration extends Disposable { fontFamily: readConfig.fontFamily, fontWeight: readConfig.fontWeight, fontSize: readConfig.fontSize, + fontFeatureSettings: readConfig.fontFeatureSettings, lineHeight: readConfig.lineHeight, letterSpacing: readConfig.letterSpacing, isMonospace: readConfig.isMonospace, @@ -249,9 +254,9 @@ class CSSBasedConfiguration extends Disposable { const maxDigitWidth = Math.max(digit0.width, digit1.width, digit2.width, digit3.width, digit4.width, digit5.width, digit6.width, digit7.width, digit8.width, digit9.width); - let isMonospace = true; + let isMonospace = (bareFontInfo.fontFeatureSettings === EditorFontLigatures.OFF); const referenceWidth = monospace[0].width; - for (let i = 1, len = monospace.length; i < len; i++) { + for (let i = 1, len = monospace.length; isMonospace && i < len; i++) { const diff = referenceWidth - monospace[i].width; if (diff < -0.001 || diff > 0.001) { isMonospace = false; @@ -276,6 +281,7 @@ class CSSBasedConfiguration extends Disposable { fontFamily: bareFontInfo.fontFamily, fontWeight: bareFontInfo.fontWeight, fontSize: bareFontInfo.fontSize, + fontFeatureSettings: bareFontInfo.fontFeatureSettings, lineHeight: bareFontInfo.lineHeight, letterSpacing: bareFontInfo.letterSpacing, isMonospace: isMonospace, @@ -294,6 +300,7 @@ export class Configuration extends CommonEditorConfiguration { domNode.style.fontFamily = fontInfo.getMassagedFontFamily(); domNode.style.fontWeight = fontInfo.fontWeight; domNode.style.fontSize = fontInfo.fontSize + 'px'; + domNode.style.fontFeatureSettings = fontInfo.fontFeatureSettings; domNode.style.lineHeight = fontInfo.lineHeight + 'px'; domNode.style.letterSpacing = fontInfo.letterSpacing + 'px'; } @@ -302,6 +309,7 @@ export class Configuration extends CommonEditorConfiguration { domNode.setFontFamily(fontInfo.getMassagedFontFamily()); domNode.setFontWeight(fontInfo.fontWeight); domNode.setFontSize(fontInfo.fontSize); + domNode.setFontFeatureSettings(fontInfo.fontFeatureSettings); domNode.setLineHeight(fontInfo.lineHeight); domNode.setLetterSpacing(fontInfo.letterSpacing); } diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index 26741a1955..daf187e664 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -65,7 +65,7 @@ export interface IPointerHandlerHelper { export class MouseHandler extends ViewEventHandler { - static MOUSE_MOVE_MINIMUM_TIME = 100; // ms + static readonly MOUSE_MOVE_MINIMUM_TIME = 100; // ms protected _context: ViewContext; protected viewController: ViewController; diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index a096fd6065..bbd0dcbbae 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -972,7 +972,7 @@ export class MouseTargetFactory { // Thank you browsers for making this so 'easy' :) - if (document.caretRangeFromPoint) { + if (typeof document.caretRangeFromPoint === 'function') { return this._doHitTestWithCaretRangeFromPoint(ctx, request); diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 6d273165ee..d0ee958956 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -11,7 +11,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import * as platform from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import { Configuration } from 'vs/editor/browser/config/configuration'; -import { CopyOptions, ICompositionData, IPasteData, ITextAreaInputHost, TextAreaInput } from 'vs/editor/browser/controller/textAreaInput'; +import { CopyOptions, ICompositionData, IPasteData, ITextAreaInputHost, TextAreaInput, ClipboardDataToCopy } from 'vs/editor/browser/controller/textAreaInput'; import { ISimpleModel, ITypeData, PagedScreenReaderStrategy, TextAreaState } from 'vs/editor/browser/controller/textAreaState'; import { ViewController } from 'vs/editor/browser/view/viewController'; import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart'; @@ -54,40 +54,6 @@ class VisibleTextAreaData { const canUseZeroSizeTextarea = (browser.isEdgeOrIE || browser.isFirefox); -interface LocalClipboardMetadata { - lastCopiedValue: string; - isFromEmptySelection: boolean; - multicursorText: string[] | null; -} - -/** - * Every time we write to the clipboard, we record a bit of extra metadata here. - * Every time we read from the cipboard, if the text matches our last written text, - * we can fetch the previous metadata. - */ -class LocalClipboardMetadataManager { - public static INSTANCE = new LocalClipboardMetadataManager(); - - private _lastState: LocalClipboardMetadata | null; - - constructor() { - this._lastState = null; - } - - public set(state: LocalClipboardMetadata | null): void { - this._lastState = state; - } - - public get(pastedText: string): LocalClipboardMetadata | null { - if (this._lastState && this._lastState.lastCopiedValue === pastedText) { - // match! - return this._lastState; - } - this._lastState = null; - return null; - } -} - export class TextAreaHandler extends ViewPart { private readonly _viewController: ViewController; @@ -172,39 +138,26 @@ export class TextAreaHandler extends ViewPart { }; const textAreaInputHost: ITextAreaInputHost = { - getPlainTextToCopy: (): string => { - const rawWhatToCopy = this._context.model.getPlainTextToCopy(this._selections, this._emptySelectionClipboard, platform.isWindows); + getDataToCopy: (generateHTML: boolean): ClipboardDataToCopy => { + const rawTextToCopy = this._context.model.getPlainTextToCopy(this._selections, this._emptySelectionClipboard, platform.isWindows); const newLineCharacter = this._context.model.getEOL(); const isFromEmptySelection = (this._emptySelectionClipboard && this._selections.length === 1 && this._selections[0].isEmpty()); - const multicursorText = (Array.isArray(rawWhatToCopy) ? rawWhatToCopy : null); - const whatToCopy = (Array.isArray(rawWhatToCopy) ? rawWhatToCopy.join(newLineCharacter) : rawWhatToCopy); + const multicursorText = (Array.isArray(rawTextToCopy) ? rawTextToCopy : null); + const text = (Array.isArray(rawTextToCopy) ? rawTextToCopy.join(newLineCharacter) : rawTextToCopy); - let metadata: LocalClipboardMetadata | null = null; - if (isFromEmptySelection || multicursorText) { - // Only store the non-default metadata - - // When writing "LINE\r\n" to the clipboard and then pasting, - // Firefox pastes "LINE\n", so let's work around this quirk - const lastCopiedValue = (browser.isFirefox ? whatToCopy.replace(/\r\n/g, '\n') : whatToCopy); - metadata = { - lastCopiedValue: lastCopiedValue, - isFromEmptySelection: (this._emptySelectionClipboard && this._selections.length === 1 && this._selections[0].isEmpty()), - multicursorText: multicursorText - }; + let html: string | null | undefined = undefined; + if (generateHTML) { + if (CopyOptions.forceCopyWithSyntaxHighlighting || (this._copyWithSyntaxHighlighting && text.length < 65536)) { + html = this._context.model.getHTMLToCopy(this._selections, this._emptySelectionClipboard); + } } - - LocalClipboardMetadataManager.INSTANCE.set(metadata); - - return whatToCopy; - }, - - getHTMLToCopy: (): string | null => { - if (!this._copyWithSyntaxHighlighting && !CopyOptions.forceCopyWithSyntaxHighlighting) { - return null; - } - - return this._context.model.getHTMLToCopy(this._selections, this._emptySelectionClipboard); + return { + isFromEmptySelection, + multicursorText, + text, + html + }; }, getScreenReaderContent: (currentState: TextAreaState): TextAreaState => { @@ -255,13 +208,11 @@ export class TextAreaHandler extends ViewPart { })); this._register(this._textAreaInput.onPaste((e: IPasteData) => { - const metadata = LocalClipboardMetadataManager.INSTANCE.get(e.text); - let pasteOnNewLine = false; let multicursorText: string[] | null = null; - if (metadata) { - pasteOnNewLine = (this._emptySelectionClipboard && metadata.isFromEmptySelection); - multicursorText = metadata.multicursorText; + if (e.metadata) { + pasteOnNewLine = (this._emptySelectionClipboard && !!e.metadata.isFromEmptySelection); + multicursorText = (typeof e.metadata.multicursorText !== 'undefined' ? e.metadata.multicursorText : null); } this._viewController.paste('keyboard', e.text, pasteOnNewLine, multicursorText); })); @@ -503,6 +454,18 @@ export class TextAreaHandler extends ViewPart { } // The primary cursor is in the viewport (at least vertically) => place textarea on the cursor + + if (platform.isMacintosh) { + // For the popup emoji input, we will make the text area as high as the line height + // We will also make the fontSize and lineHeight the correct dimensions to help with the placement of these pickers + this._renderInsideEditor( + top, left, + canUseZeroSizeTextarea ? 0 : 1, this._lineHeight, + true + ); + return; + } + this._renderInsideEditor( top, left, canUseZeroSizeTextarea ? 0 : 1, canUseZeroSizeTextarea ? 0 : 1, diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index 5ae7510e36..b6098cec5f 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -32,11 +32,24 @@ const enum ReadFromTextArea { export interface IPasteData { text: string; + metadata: ClipboardStoredMetadata | null; +} + +export interface ClipboardDataToCopy { + isFromEmptySelection: boolean; + multicursorText: string[] | null | undefined; + text: string; + html: string | null | undefined; +} + +export interface ClipboardStoredMetadata { + version: 1; + isFromEmptySelection: boolean | undefined; + multicursorText: string[] | null | undefined; } export interface ITextAreaInputHost { - getPlainTextToCopy(): string; - getHTMLToCopy(): string | null; + getDataToCopy(html: boolean): ClipboardDataToCopy; getScreenReaderContent(currentState: TextAreaState): TextAreaState; deduceModelPosition(viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position; } @@ -59,6 +72,39 @@ interface CompositionEvent extends UIEvent { readonly locale: string; } +interface InMemoryClipboardMetadata { + lastCopiedValue: string; + data: ClipboardStoredMetadata; +} + +/** + * Every time we write to the clipboard, we record a bit of extra metadata here. + * Every time we read from the cipboard, if the text matches our last written text, + * we can fetch the previous metadata. + */ +class InMemoryClipboardMetadataManager { + public static readonly INSTANCE = new InMemoryClipboardMetadataManager(); + + private _lastState: InMemoryClipboardMetadata | null; + + constructor() { + this._lastState = null; + } + + public set(lastCopiedValue: string, data: ClipboardStoredMetadata): void { + this._lastState = { lastCopiedValue, data }; + } + + public get(pastedText: string): ClipboardStoredMetadata | null { + if (this._lastState && this._lastState.lastCopiedValue === pastedText) { + // match! + return this._lastState.data; + } + this._lastState = null; + return null; + } +} + /** * Writes screen reader content to the textarea and is able to analyze its input events to generate: * - onCut @@ -279,9 +325,7 @@ export class TextAreaInput extends Disposable { } } else { if (typeInput.text !== '') { - this._onPaste.fire({ - text: typeInput.text - }); + this._firePaste(typeInput.text, null); } this._nextCommand = ReadFromTextArea.Type; } @@ -314,11 +358,9 @@ export class TextAreaInput extends Disposable { this._textArea.setIgnoreSelectionChangeTime('received paste event'); if (ClipboardEventUtils.canUseTextData(e)) { - const pastePlainText = ClipboardEventUtils.getTextData(e); + const [pastePlainText, metadata] = ClipboardEventUtils.getTextData(e); if (pastePlainText !== '') { - this._onPaste.fire({ - text: pastePlainText - }); + this._firePaste(pastePlainText, metadata); } } else { if (this._textArea.getSelectionStart() !== this._textArea.getSelectionEnd()) { @@ -491,19 +533,38 @@ export class TextAreaInput extends Disposable { } private _ensureClipboardGetsEditorSelection(e: ClipboardEvent): void { - const copyPlainText = this._host.getPlainTextToCopy(); + const dataToCopy = this._host.getDataToCopy(ClipboardEventUtils.canUseTextData(e) && browser.hasClipboardSupport()); + const storedMetadata: ClipboardStoredMetadata = { + version: 1, + isFromEmptySelection: dataToCopy.isFromEmptySelection, + multicursorText: dataToCopy.multicursorText + }; + InMemoryClipboardMetadataManager.INSTANCE.set( + // When writing "LINE\r\n" to the clipboard and then pasting, + // Firefox pastes "LINE\n", so let's work around this quirk + (browser.isFirefox ? dataToCopy.text.replace(/\r\n/g, '\n') : dataToCopy.text), + storedMetadata + ); + if (!ClipboardEventUtils.canUseTextData(e)) { // Looks like an old browser. The strategy is to place the text // we'd like to be copied to the clipboard in the textarea and select it. - this._setAndWriteTextAreaState('copy or cut', TextAreaState.selectedText(copyPlainText)); + this._setAndWriteTextAreaState('copy or cut', TextAreaState.selectedText(dataToCopy.text)); return; } - let copyHTML: string | null = null; - if (browser.hasClipboardSupport() && (copyPlainText.length < 65536 || CopyOptions.forceCopyWithSyntaxHighlighting)) { - copyHTML = this._host.getHTMLToCopy(); + ClipboardEventUtils.setTextData(e, dataToCopy.text, dataToCopy.html, storedMetadata); + } + + private _firePaste(text: string, metadata: ClipboardStoredMetadata | null): void { + if (!metadata) { + // try the in-memory store + metadata = InMemoryClipboardMetadataManager.INSTANCE.get(text); } - ClipboardEventUtils.setTextData(e, copyPlainText, copyHTML); + this._onPaste.fire({ + text: text, + metadata: metadata + }); } } @@ -519,10 +580,25 @@ class ClipboardEventUtils { return false; } - public static getTextData(e: ClipboardEvent): string { + public static getTextData(e: ClipboardEvent): [string, ClipboardStoredMetadata | null] { if (e.clipboardData) { e.preventDefault(); - return e.clipboardData.getData('text/plain'); + + const text = e.clipboardData.getData('text/plain'); + let metadata: ClipboardStoredMetadata | null = null; + const rawmetadata = e.clipboardData.getData('vscode-editor-data'); + if (typeof rawmetadata === 'string') { + try { + metadata = JSON.parse(rawmetadata); + if (metadata.version !== 1) { + metadata = null; + } + } catch (err) { + // no problem! + } + } + + return [text, metadata]; } if ((window).clipboardData) { @@ -533,12 +609,13 @@ class ClipboardEventUtils { throw new Error('ClipboardEventUtils.getTextData: Cannot use text data!'); } - public static setTextData(e: ClipboardEvent, text: string, richText: string | null): void { + public static setTextData(e: ClipboardEvent, text: string, html: string | null | undefined, metadata: ClipboardStoredMetadata): void { if (e.clipboardData) { e.clipboardData.setData('text/plain', text); - if (richText !== null) { - e.clipboardData.setData('text/html', richText); + if (typeof html === 'string') { + e.clipboardData.setData('text/html', html); } + e.clipboardData.setData('vscode-editor-data', JSON.stringify(metadata)); e.preventDefault(); return; } diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index ebbb8dc5f2..4d49958cfa 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -18,6 +18,7 @@ import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguag import { OverviewRulerZone } from 'vs/editor/common/view/overviewZoneManager'; import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IDiffComputationResult } from 'vs/editor/common/services/editorWorkerService'; /** * A view zone is a full horizontal rectangle that 'pushes' text down. @@ -834,6 +835,15 @@ export interface IDiffLineInformation { readonly equivalentLineNumber: number; } +/** + * @internal + */ +export const enum DiffEditorState { + Idle, + ComputingDiff, + DiffComputed +} + /** * A rich diff editor. */ @@ -854,6 +864,11 @@ export interface IDiffEditor extends editorCommon.IEditor { * @internal */ readonly renderIndicators: boolean; + /** + * Timeout in milliseconds after which diff computation is cancelled. + * @internal + */ + readonly maxComputationTime: number; /** * @see ICodeEditor.getDomNode @@ -906,6 +921,12 @@ export interface IDiffEditor extends editorCommon.IEditor { */ getLineChanges(): editorCommon.ILineChange[] | null; + /** + * Get the computed diff information. + * @internal + */ + getDiffComputationResult(): IDiffComputationResult | null; + /** * Get information based on computed diff about a line number from the original model. * If the diff computation is not finished or the model is missing, will return null. diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index ed6bc6cc43..0c281a235c 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -6,28 +6,35 @@ import { IPosition } from 'vs/base/browser/ui/contextview/contextview'; import { illegalArgument } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { Position } from 'vs/editor/common/core/position'; -import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { IEditorContribution, IDiffEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IConstructorSignature1, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IConstructorSignature1, ServicesAccessor as InstantiationServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindings, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { withNullAsUndefined } from 'vs/base/common/types'; -export type ServicesAccessor = ServicesAccessor; +export type ServicesAccessor = InstantiationServicesAccessor; export type IEditorContributionCtor = IConstructorSignature1; -export type EditorTelemetryDataFragment = { - target: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - snippet: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; -}; +export type IDiffEditorContributionCtor = IConstructorSignature1; + +export interface IEditorContributionDescription { + id: string; + ctor: IEditorContributionCtor; +} + +export interface IDiffEditorContributionDescription { + id: string; + ctor: IDiffEditorContributionCtor; +} //#region Command @@ -224,8 +231,8 @@ export abstract class EditorAction extends EditorCommand { protected reportTelemetry(accessor: ServicesAccessor, editor: ICodeEditor) { type EditorActionInvokedClassification = { - name: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - id: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + name: { classification: 'SystemMetaData', purpose: 'FeatureInsight', }; + id: { classification: 'SystemMetaData', purpose: 'FeatureInsight', }; }; type EditorActionInvokedEvent = { name: string; @@ -241,7 +248,7 @@ export abstract class EditorAction extends EditorCommand { // --- Registration of commands and actions -export function registerLanguageCommand(id: string, handler: (accessor: ServicesAccessor, args: Args) => any) { +export function registerLanguageCommand(id: string, handler: (accessor: ServicesAccessor, args: Args) => any) { CommandsRegistry.registerCommand(id, (accessor, args) => handler(accessor, args || {})); } @@ -296,8 +303,12 @@ export function registerInstantiatedEditorAction(editorAction: EditorAction): vo EditorContributionRegistry.INSTANCE.registerEditorAction(editorAction); } -export function registerEditorContribution(ctor: IEditorContributionCtor): void { - EditorContributionRegistry.INSTANCE.registerEditorContribution(ctor); +export function registerEditorContribution(id: string, ctor: IEditorContributionCtor): void { + EditorContributionRegistry.INSTANCE.registerEditorContribution(id, ctor); +} + +export function registerDiffEditorContribution(id: string, ctor: IDiffEditorContributionCtor): void { + EditorContributionRegistry.INSTANCE.registerDiffEditorContribution(id, ctor); } export namespace EditorExtensionsRegistry { @@ -310,9 +321,17 @@ export namespace EditorExtensionsRegistry { return EditorContributionRegistry.INSTANCE.getEditorActions(); } - export function getEditorContributions(): IEditorContributionCtor[] { + export function getEditorContributions(): IEditorContributionDescription[] { return EditorContributionRegistry.INSTANCE.getEditorContributions(); } + + export function getSomeEditorContributions(ids: string[]): IEditorContributionDescription[] { + return EditorContributionRegistry.INSTANCE.getEditorContributions().filter(c => ids.indexOf(c.id) >= 0); + } + + export function getDiffEditorContributions(): IDiffEditorContributionDescription[] { + return EditorContributionRegistry.INSTANCE.getDiffEditorContributions(); + } } // Editor extension points @@ -324,18 +343,32 @@ class EditorContributionRegistry { public static readonly INSTANCE = new EditorContributionRegistry(); - private readonly editorContributions: IEditorContributionCtor[]; + private readonly editorContributions: IEditorContributionDescription[]; + private readonly diffEditorContributions: IDiffEditorContributionDescription[]; private readonly editorActions: EditorAction[]; private readonly editorCommands: { [commandId: string]: EditorCommand; }; constructor() { this.editorContributions = []; + this.diffEditorContributions = []; this.editorActions = []; this.editorCommands = Object.create(null); } - public registerEditorContribution(ctor: IEditorContributionCtor): void { - this.editorContributions.push(ctor); + public registerEditorContribution(id: string, ctor: IEditorContributionCtor): void { + this.editorContributions.push({ id, ctor }); + } + + public getEditorContributions(): IEditorContributionDescription[] { + return this.editorContributions.slice(0); + } + + public registerDiffEditorContribution(id: string, ctor: IDiffEditorContributionCtor): void { + this.diffEditorContributions.push({ id, ctor }); + } + + public getDiffEditorContributions(): IDiffEditorContributionDescription[] { + return this.diffEditorContributions.slice(0); } public registerEditorAction(action: EditorAction) { @@ -343,10 +376,6 @@ class EditorContributionRegistry { this.editorActions.push(action); } - public getEditorContributions(): IEditorContributionCtor[] { - return this.editorContributions.slice(0); - } - public getEditorActions(): EditorAction[] { return this.editorActions.slice(0); } diff --git a/src/vs/editor/browser/services/abstractCodeEditorService.ts b/src/vs/editor/browser/services/abstractCodeEditorService.ts index dca4615d6c..945cd8b2d7 100644 --- a/src/vs/editor/browser/services/abstractCodeEditorService.ts +++ b/src/vs/editor/browser/services/abstractCodeEditorService.ts @@ -70,8 +70,8 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC return Object.keys(this._diffEditors).map(id => this._diffEditors[id]); } - getFocusedCodeEditor(): ICodeEditor | undefined { - let editorWithWidgetFocus: ICodeEditor | undefined; + getFocusedCodeEditor(): ICodeEditor | null { + let editorWithWidgetFocus: ICodeEditor | null = null; const editors = this.listCodeEditors(); for (const editor of editors) { @@ -124,8 +124,8 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC delete this._transientWatchers[w.uri]; } - abstract getActiveCodeEditor(): ICodeEditor | undefined; - abstract openCodeEditor(input: IResourceInput, source: ICodeEditor | undefined, sideBySide?: boolean): Promise; + abstract getActiveCodeEditor(): ICodeEditor | null; + abstract openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; } export class ModelTransientSettingWatcher { diff --git a/src/vs/editor/browser/services/codeEditorService.ts b/src/vs/editor/browser/services/codeEditorService.ts index fc861b28b5..edf9a1081a 100644 --- a/src/vs/editor/browser/services/codeEditorService.ts +++ b/src/vs/editor/browser/services/codeEditorService.ts @@ -33,10 +33,9 @@ export interface ICodeEditorService { listDiffEditors(): readonly IDiffEditor[]; /** - * Returns the current focused code editor (if the focus is in the editor or in an editor widget) or - * `undefined` if none. + * Returns the current focused code editor (if the focus is in the editor or in an editor widget) or null. */ - getFocusedCodeEditor(): ICodeEditor | undefined; + getFocusedCodeEditor(): ICodeEditor | null; registerDecorationType(key: string, options: IDecorationRenderOptions, parentTypeKey?: string): void; removeDecorationType(key: string): void; @@ -45,6 +44,6 @@ export interface ICodeEditorService { setTransientModelProperty(model: ITextModel, key: string, value: any): void; getTransientModelProperty(model: ITextModel, key: string): any; - getActiveCodeEditor(): ICodeEditor | undefined; - openCodeEditor(input: IResourceInput, source: ICodeEditor | undefined, sideBySide?: boolean): Promise; + getActiveCodeEditor(): ICodeEditor | null; + openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; } diff --git a/src/vs/editor/browser/services/codeEditorServiceImpl.ts b/src/vs/editor/browser/services/codeEditorServiceImpl.ts index a4993570fc..1840d47596 100644 --- a/src/vs/editor/browser/services/codeEditorServiceImpl.ts +++ b/src/vs/editor/browser/services/codeEditorServiceImpl.ts @@ -65,8 +65,8 @@ export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService { return provider.getOptions(this, writable); } - abstract getActiveCodeEditor(): ICodeEditor | undefined; - abstract openCodeEditor(input: IResourceInput, source: ICodeEditor | undefined, sideBySide?: boolean): Promise; + abstract getActiveCodeEditor(): ICodeEditor | null; + abstract openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; } interface IModelDecorationOptionsProvider extends IDisposable { diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index d5eb21cd8c..3b49459671 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -14,6 +14,7 @@ import { URI } from 'vs/base/common/uri'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { IOpener, IOpenerService, IValidator, IExternalUriResolver, OpenOptions } from 'vs/platform/opener/common/opener'; +import { EditorOpenContext } from 'vs/platform/editor/common/editor'; export class OpenerService extends Disposable implements IOpenerService { @@ -32,20 +33,24 @@ export class OpenerService extends Disposable implements IOpenerService { registerOpener(opener: IOpener): IDisposable { const remove = this._openers.push(opener); + return { dispose: remove }; } registerValidator(validator: IValidator): IDisposable { const remove = this._validators.push(validator); + return { dispose: remove }; } registerExternalUriResolver(resolver: IExternalUriResolver): IDisposable { const remove = this._resolvers.push(resolver); + return { dispose: remove }; } async open(resource: URI, options?: OpenOptions): Promise { + // no scheme ?!? if (!resource.scheme) { return Promise.resolve(false); @@ -70,21 +75,21 @@ export class OpenerService extends Disposable implements IOpenerService { return this._doOpen(resource, options); } - public async resolveExternalUri(resource: URI, options?: { readonly allowTunneling?: boolean }): Promise<{ resolved: URI, dispose(): void }> { + async resolveExternalUri(resource: URI, options?: { readonly allowTunneling?: boolean }): Promise<{ resolved: URI, dispose(): void }> { for (const resolver of this._resolvers.toArray()) { const result = await resolver.resolveExternalUri(resource, options); if (result) { return result; } } + return { resolved: resource, dispose: () => { } }; } - private _doOpen(resource: URI, options: OpenOptions | undefined): Promise { - + private async _doOpen(resource: URI, options: OpenOptions | undefined): Promise { const { scheme, path, query, fragment } = resource; - if (equalsIgnoreCase(scheme, Schemas.mailto) || (options && options.openExternal)) { + if (equalsIgnoreCase(scheme, Schemas.mailto) || options?.openExternal) { // open default mail application return this._doOpenExternal(resource, options); } @@ -92,7 +97,9 @@ export class OpenerService extends Disposable implements IOpenerService { if (equalsIgnoreCase(scheme, Schemas.http) || equalsIgnoreCase(scheme, Schemas.https)) { // open link in default browser return this._doOpenExternal(resource, options); - } else if (equalsIgnoreCase(scheme, Schemas.command)) { + } + + if (equalsIgnoreCase(scheme, Schemas.command)) { // run command or bail out if command isn't known if (!CommandsRegistry.getCommand(path)) { return Promise.reject(`command '${path}' NOT known`); @@ -105,40 +112,46 @@ export class OpenerService extends Disposable implements IOpenerService { args = [args]; } } catch (e) { - // - } - return this._commandService.executeCommand(path, ...args).then(() => true); - - } else { - let selection: { startLineNumber: number; startColumn: number; } | undefined = undefined; - const match = /^L?(\d+)(?:,(\d+))?/.exec(fragment); - if (match) { - // support file:///some/file.js#73,84 - // support file:///some/file.js#L73 - selection = { - startLineNumber: parseInt(match[1]), - startColumn: match[2] ? parseInt(match[2]) : 1 - }; - // remove fragment - resource = resource.with({ fragment: '' }); + // ignore error } - if (resource.scheme === Schemas.file) { - resource = resources.normalizePath(resource); // workaround for non-normalized paths (https://github.com/Microsoft/vscode/issues/12954) - } + await this._commandService.executeCommand(path, ...args); - return this._editorService.openCodeEditor( - { resource, options: { selection, } }, - this._editorService.getFocusedCodeEditor(), - options && options.openToSide - ).then(() => true); + return true; } + + // finally open in editor + let selection: { startLineNumber: number; startColumn: number; } | undefined = undefined; + const match = /^L?(\d+)(?:,(\d+))?/.exec(fragment); + if (match) { + // support file:///some/file.js#73,84 + // support file:///some/file.js#L73 + selection = { + startLineNumber: parseInt(match[1]), + startColumn: match[2] ? parseInt(match[2]) : 1 + }; + // remove fragment + resource = resource.with({ fragment: '' }); + } + + if (resource.scheme === Schemas.file) { + resource = resources.normalizePath(resource); // workaround for non-normalized paths (https://github.com/Microsoft/vscode/issues/12954) + } + + await this._editorService.openCodeEditor( + { resource, options: { selection, context: options?.fromUserGesture ? EditorOpenContext.USER : EditorOpenContext.API } }, + this._editorService.getFocusedCodeEditor(), + options?.openToSide + ); + + return true; } private async _doOpenExternal(resource: URI, options: OpenOptions | undefined): Promise { const { resolved } = await this.resolveExternalUri(resource, options); dom.windowOpenNoOpener(encodeURI(resolved.toString(true))); - return Promise.resolve(true); + + return true; } dispose() { diff --git a/src/vs/editor/browser/view/viewController.ts b/src/vs/editor/browser/view/viewController.ts index 12a1849932..c25248f8e9 100644 --- a/src/vs/editor/browser/view/viewController.ts +++ b/src/vs/editor/browser/view/viewController.ts @@ -13,6 +13,7 @@ import { IConfiguration } from 'vs/editor/common/editorCommon'; import { IViewModel } from 'vs/editor/common/viewModel/viewModel'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import * as platform from 'vs/base/common/platform'; export interface IMouseDispatchData { position: Position; @@ -132,7 +133,8 @@ export class ViewController { } public dispatchMouse(data: IMouseDispatchData): void { - if (data.middleButton) { + const selectionClipboardIsOn = (platform.isLinux && this.configuration.options.get(EditorOption.selectionClipboard)); + if (data.middleButton && !selectionClipboardIsOn) { this._columnSelect(data.position, data.mouseColumn, data.inSelectionMode); } else if (data.startedOnLineNumbers) { // If the dragging started on the gutter, then have operations work on the entire line diff --git a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts index dcba54f657..4e0993c54f 100644 --- a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts +++ b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts @@ -9,7 +9,7 @@ import { ContentWidgetPositionPreference, IContentWidget } from 'vs/editor/brows import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { Constants } from 'vs/editor/common/core/uint'; +import { Constants } from 'vs/base/common/uint'; import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext'; import { ViewContext } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; @@ -380,7 +380,7 @@ class Widget { belowLeft = absoluteBelowLeft; } - return { fitsAbove, aboveTop, aboveLeft, fitsBelow, belowTop, belowLeft }; + return { fitsAbove, aboveTop: Math.max(aboveTop, TOP_PADDING), aboveLeft, fitsBelow, belowTop, belowLeft }; } private _prepareRenderWidgetAtExactPositionOverflowing(topLeft: Coordinate): Coordinate { diff --git a/src/vs/editor/browser/viewParts/lines/rangeUtil.ts b/src/vs/editor/browser/viewParts/lines/rangeUtil.ts index 05fca5ec32..03673d4b61 100644 --- a/src/vs/editor/browser/viewParts/lines/rangeUtil.ts +++ b/src/vs/editor/browser/viewParts/lines/rangeUtil.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Constants } from 'vs/editor/common/core/uint'; +import { Constants } from 'vs/base/common/uint'; import { HorizontalRange } from 'vs/editor/common/view/renderingContext'; class FloatHorizontalRange { diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index c8eba1d667..e1a506d536 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -16,7 +16,7 @@ import { CharacterMapping, ForeignElementType, RenderLineInput, renderViewLine, import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; import { InlineDecorationType } from 'vs/editor/common/viewModel/viewModel'; import { HIGH_CONTRAST, ThemeType } from 'vs/platform/theme/common/themeService'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { EditorOption, EditorFontLigatures } from 'vs/editor/common/config/editorOptions'; const canUseFastRenderedViewLine = (function () { if (platform.isNative) { @@ -77,7 +77,7 @@ export class ViewLineOptions { public readonly canUseHalfwidthRightwardsArrow: boolean; public readonly lineHeight: number; public readonly stopRenderingLineAfter: number; - public readonly fontLigatures: boolean; + public readonly fontLigatures: string; constructor(config: IConfiguration, themeType: ThemeType) { this.themeType = themeType; @@ -89,7 +89,6 @@ export class ViewLineOptions { this.useMonospaceOptimizations = ( fontInfo.isMonospace && !options.get(EditorOption.disableMonospaceOptimizations) - && !options.get(EditorOption.fontLigatures) ); this.canUseHalfwidthRightwardsArrow = fontInfo.canUseHalfwidthRightwardsArrow; this.lineHeight = options.get(EditorOption.lineHeight); @@ -218,7 +217,7 @@ export class ViewLine implements IVisibleLine { options.stopRenderingLineAfter, options.renderWhitespace, options.renderControlCharacters, - options.fontLigatures, + options.fontLigatures !== EditorFontLigatures.OFF, selectionsOnLine ); diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.ts b/src/vs/editor/browser/viewParts/lines/viewLines.ts index e30dbae238..5710667d8b 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLines.ts @@ -20,6 +20,7 @@ import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; import { Viewport } from 'vs/editor/common/viewModel/viewModel'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { Constants } from 'vs/base/common/uint'; class LastRenderedData { @@ -641,7 +642,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, const viewportEndX = viewportStartX + viewport.width; const visibleRanges = this.visibleRangesForRange2(new Range(lineNumber, startColumn, lineNumber, endColumn)); - let boxStartX = Number.MAX_VALUE; + let boxStartX = Constants.MAX_SAFE_SMALL_INTEGER; let boxEndX = 0; if (!visibleRanges) { diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 309b285e24..974b15eb6d 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -18,9 +18,10 @@ import { Range } from 'vs/editor/common/core/range'; import { RGBA8 } from 'vs/editor/common/core/rgba'; import { IConfiguration, ScrollType } from 'vs/editor/common/editorCommon'; import { ColorId } from 'vs/editor/common/modes'; -import { Constants, MinimapCharRenderer, MinimapTokensColorTracker } from 'vs/editor/common/view/minimapCharRenderer'; +import { MinimapCharRenderer } from 'vs/editor/browser/viewParts/minimap/minimapCharRenderer'; +import { Constants } from 'vs/editor/browser/viewParts/minimap/minimapCharSheet'; +import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTokensColorTracker'; import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext'; -import { getOrCreateMinimapCharRenderer } from 'vs/editor/common/view/runtimeMinimapCharRenderer'; import { ViewContext } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { ViewLineData } from 'vs/editor/common/viewModel/viewModel'; @@ -30,33 +31,22 @@ import { ModelDecorationMinimapOptions } from 'vs/editor/common/model/textModel' import { Selection } from 'vs/editor/common/core/selection'; import { Color } from 'vs/base/common/color'; import { GestureEvent, EventType, Gesture } from 'vs/base/browser/touch'; +import { MinimapCharRendererFactory } from 'vs/editor/browser/viewParts/minimap/minimapCharRendererFactory'; -function getMinimapLineHeight(renderMinimap: RenderMinimap): number { - if (renderMinimap === RenderMinimap.Large) { - return Constants.x2_CHAR_HEIGHT; +function getMinimapLineHeight(renderMinimap: RenderMinimap, scale: number): number { + if (renderMinimap === RenderMinimap.Text) { + return Constants.BASE_CHAR_HEIGHT * scale; } - if (renderMinimap === RenderMinimap.LargeBlocks) { - return Constants.x2_CHAR_HEIGHT + 2; - } - if (renderMinimap === RenderMinimap.Small) { - return Constants.x1_CHAR_HEIGHT; - } - // RenderMinimap.SmallBlocks - return Constants.x1_CHAR_HEIGHT + 1; + // RenderMinimap.Blocks + return (Constants.BASE_CHAR_HEIGHT + 1) * scale; } -function getMinimapCharWidth(renderMinimap: RenderMinimap): number { - if (renderMinimap === RenderMinimap.Large) { - return Constants.x2_CHAR_WIDTH; +function getMinimapCharWidth(renderMinimap: RenderMinimap, scale: number): number { + if (renderMinimap === RenderMinimap.Text) { + return Constants.BASE_CHAR_WIDTH * scale; } - if (renderMinimap === RenderMinimap.LargeBlocks) { - return Constants.x2_CHAR_WIDTH; - } - if (renderMinimap === RenderMinimap.Small) { - return Constants.x1_CHAR_WIDTH; - } - // RenderMinimap.SmallBlocks - return Constants.x1_CHAR_WIDTH; + // RenderMinimap.Blocks + return Constants.BASE_CHAR_WIDTH * scale; } /** @@ -78,6 +68,10 @@ class MinimapOptions { public readonly lineHeight: number; + public readonly fontScale: number; + + public readonly charRenderer: MinimapCharRenderer; + /** * container dom node left position (in CSS px) */ @@ -119,6 +113,8 @@ class MinimapOptions { this.scrollBeyondLastLine = options.get(EditorOption.scrollBeyondLastLine); const minimapOpts = options.get(EditorOption.minimap); this.showSlider = minimapOpts.showSlider; + this.fontScale = Math.round(minimapOpts.scale * pixelRatio); + this.charRenderer = MinimapCharRendererFactory.create(this.fontScale, fontInfo.fontFamily); this.pixelRatio = pixelRatio; this.typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth; this.lineHeight = options.get(EditorOption.lineHeight); @@ -140,6 +136,7 @@ class MinimapOptions { && this.pixelRatio === other.pixelRatio && this.typicalHalfwidthCharacterWidth === other.typicalHalfwidthCharacterWidth && this.lineHeight === other.lineHeight + && this.fontScale === other.fontScale && this.minimapLeft === other.minimapLeft && this.minimapWidth === other.minimapWidth && this.minimapHeight === other.minimapHeight @@ -225,7 +222,7 @@ class MinimapLayout { previousLayout: MinimapLayout | null ): MinimapLayout { const pixelRatio = options.pixelRatio; - const minimapLineHeight = getMinimapLineHeight(options.renderMinimap); + const minimapLineHeight = getMinimapLineHeight(options.renderMinimap, options.fontScale); const minimapLinesFitting = Math.floor(options.canvasInnerHeight / minimapLineHeight); const lineHeight = options.lineHeight; @@ -524,7 +521,7 @@ export class Minimap extends ViewPart { if (!this._lastRenderData) { return; } - const minimapLineHeight = getMinimapLineHeight(renderMinimap); + const minimapLineHeight = getMinimapLineHeight(renderMinimap, this._options.fontScale); const internalOffsetY = this._options.pixelRatio * e.browserEvent.offsetY; const lineIndex = Math.floor(internalOffsetY / minimapLineHeight); @@ -778,7 +775,7 @@ export class Minimap extends ViewPart { // Compute horizontal slider coordinates const scrollLeftChars = renderingCtx.scrollLeft / this._options.typicalHalfwidthCharacterWidth; - const horizontalSliderLeft = Math.min(this._options.minimapWidth, Math.round(scrollLeftChars * getMinimapCharWidth(this._options.renderMinimap) / this._options.pixelRatio)); + const horizontalSliderLeft = Math.min(this._options.minimapWidth, Math.round(scrollLeftChars * getMinimapCharWidth(this._options.renderMinimap, this._options.fontScale) / this._options.pixelRatio)); this._sliderHorizontal.setLeft(horizontalSliderLeft); this._sliderHorizontal.setWidth(this._options.minimapWidth - horizontalSliderLeft); this._sliderHorizontal.setTop(0); @@ -794,8 +791,8 @@ export class Minimap extends ViewPart { const decorations = this._context.model.getDecorationsInViewport(new Range(layout.startLineNumber, 1, layout.endLineNumber, this._context.model.getLineMaxColumn(layout.endLineNumber))); const { renderMinimap, canvasInnerWidth, canvasInnerHeight } = this._options; - const lineHeight = getMinimapLineHeight(renderMinimap); - const characterWidth = getMinimapCharWidth(renderMinimap); + const lineHeight = getMinimapLineHeight(renderMinimap, this._options.fontScale); + const characterWidth = getMinimapCharWidth(renderMinimap, this._options.fontScale); const tabSize = this._context.model.getOptions().tabSize; const canvasContext = this._decorationsCanvas.domNode.getContext('2d')!; @@ -890,7 +887,7 @@ export class Minimap extends ViewPart { const renderMinimap = this._options.renderMinimap; const startLineNumber = layout.startLineNumber; const endLineNumber = layout.endLineNumber; - const minimapLineHeight = getMinimapLineHeight(renderMinimap); + const minimapLineHeight = getMinimapLineHeight(renderMinimap, this._options.fontScale); // Check if nothing changed w.r.t. lines from last frame if (this._lastRenderData && this._lastRenderData.linesEquals(layout)) { @@ -929,10 +926,11 @@ export class Minimap extends ViewPart { useLighterFont, renderMinimap, this._tokensColorTracker, - getOrCreateMinimapCharRenderer(), + this._options.charRenderer, dy, tabSize, - lineInfo.data[lineIndex]! + lineInfo.data[lineIndex]!, + this._options.fontScale ); } renderedLines[lineIndex] = new MinimapLine(dy); @@ -1056,11 +1054,12 @@ export class Minimap extends ViewPart { minimapCharRenderer: MinimapCharRenderer, dy: number, tabSize: number, - lineData: ViewLineData + lineData: ViewLineData, + fontScale: number ): void { const content = lineData.content; const tokens = lineData.tokens; - const charWidth = getMinimapCharWidth(renderMinimap); + const charWidth = getMinimapCharWidth(renderMinimap, fontScale); const maxDx = target.width - charWidth; let dx = 0; @@ -1092,16 +1091,12 @@ export class Minimap extends ViewPart { const count = strings.isFullWidthCharacter(charCode) ? 2 : 1; for (let i = 0; i < count; i++) { - if (renderMinimap === RenderMinimap.Large) { - minimapCharRenderer.x2RenderChar(target, dx, dy, charCode, tokenColor, backgroundColor, useLighterFont); - } else if (renderMinimap === RenderMinimap.Small) { - minimapCharRenderer.x1RenderChar(target, dx, dy, charCode, tokenColor, backgroundColor, useLighterFont); - } else if (renderMinimap === RenderMinimap.LargeBlocks) { - minimapCharRenderer.x2BlockRenderChar(target, dx, dy, tokenColor, backgroundColor, useLighterFont); - } else { - // RenderMinimap.SmallBlocks - minimapCharRenderer.x1BlockRenderChar(target, dx, dy, tokenColor, backgroundColor, useLighterFont); + if (renderMinimap === RenderMinimap.Blocks) { + minimapCharRenderer.blockRenderChar(target, dx, dy, tokenColor, backgroundColor, useLighterFont); + } else { // RenderMinimap.Text + minimapCharRenderer.renderChar(target, dx, dy, charCode, tokenColor, backgroundColor, useLighterFont); } + dx += charWidth; if (dx > maxDx) { diff --git a/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts b/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts new file mode 100644 index 0000000000..03ec73e22e --- /dev/null +++ b/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts @@ -0,0 +1,121 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { RGBA8 } from 'vs/editor/common/core/rgba'; +import { Constants, getCharIndex } from './minimapCharSheet'; + +export class MinimapCharRenderer { + _minimapCharRendererBrand: void; + + private readonly charDataNormal: Uint8ClampedArray; + private readonly charDataLight: Uint8ClampedArray; + + constructor(charData: Uint8ClampedArray, public readonly scale: number) { + this.charDataNormal = MinimapCharRenderer.soften(charData, 12 / 15); + this.charDataLight = MinimapCharRenderer.soften(charData, 50 / 60); + } + + private static soften(input: Uint8ClampedArray, ratio: number): Uint8ClampedArray { + let result = new Uint8ClampedArray(input.length); + for (let i = 0, len = input.length; i < len; i++) { + result[i] = input[i] * ratio; + } + return result; + } + + public renderChar( + target: ImageData, + dx: number, + dy: number, + chCode: number, + color: RGBA8, + backgroundColor: RGBA8, + useLighterFont: boolean + ): void { + const charWidth = Constants.BASE_CHAR_WIDTH * this.scale; + const charHeight = Constants.BASE_CHAR_HEIGHT * this.scale; + if (dx + charWidth > target.width || dy + charHeight > target.height) { + console.warn('bad render request outside image data'); + return; + } + + const charData = useLighterFont ? this.charDataLight : this.charDataNormal; + const charIndex = getCharIndex(chCode); + + const destWidth = target.width * Constants.RGBA_CHANNELS_CNT; + + const backgroundR = backgroundColor.r; + const backgroundG = backgroundColor.g; + const backgroundB = backgroundColor.b; + + const deltaR = color.r - backgroundR; + const deltaG = color.g - backgroundG; + const deltaB = color.b - backgroundB; + + const dest = target.data; + let sourceOffset = charIndex * charWidth * charHeight; + + let row = dy * destWidth + dx * Constants.RGBA_CHANNELS_CNT; + for (let y = 0; y < charHeight; y++) { + let column = row; + for (let x = 0; x < charWidth; x++) { + const c = charData[sourceOffset++] / 255; + dest[column++] = backgroundR + deltaR * c; + dest[column++] = backgroundG + deltaG * c; + dest[column++] = backgroundB + deltaB * c; + column++; + } + + row += destWidth; + } + } + + public blockRenderChar( + target: ImageData, + dx: number, + dy: number, + color: RGBA8, + backgroundColor: RGBA8, + useLighterFont: boolean + ): void { + const charWidth = Constants.BASE_CHAR_WIDTH * this.scale; + const charHeight = Constants.BASE_CHAR_HEIGHT * this.scale; + if (dx + charWidth > target.width || dy + charHeight > target.height) { + console.warn('bad render request outside image data'); + return; + } + + const destWidth = target.width * Constants.RGBA_CHANNELS_CNT; + + const c = 0.5; + + const backgroundR = backgroundColor.r; + const backgroundG = backgroundColor.g; + const backgroundB = backgroundColor.b; + + const deltaR = color.r - backgroundR; + const deltaG = color.g - backgroundG; + const deltaB = color.b - backgroundB; + + const colorR = backgroundR + deltaR * c; + const colorG = backgroundG + deltaG * c; + const colorB = backgroundB + deltaB * c; + + const dest = target.data; + + let row = dy * destWidth + dx * Constants.RGBA_CHANNELS_CNT; + for (let y = 0; y < charHeight; y++) { + let column = row; + for (let x = 0; x < charWidth; x++) { + dest[column++] = colorR; + dest[column++] = colorG; + dest[column++] = colorB; + column++; + } + + row += destWidth; + } + } +} diff --git a/src/vs/editor/browser/viewParts/minimap/minimapCharRendererFactory.ts b/src/vs/editor/browser/viewParts/minimap/minimapCharRendererFactory.ts new file mode 100644 index 0000000000..5b8a80917d --- /dev/null +++ b/src/vs/editor/browser/viewParts/minimap/minimapCharRendererFactory.ts @@ -0,0 +1,161 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { MinimapCharRenderer } from 'vs/editor/browser/viewParts/minimap/minimapCharRenderer'; +import { allCharCodes } from 'vs/editor/browser/viewParts/minimap/minimapCharSheet'; +import { Constants } from './minimapCharSheet'; + +/** + * Creates character renderers. It takes a 'scale' that determines how large + * characters should be drawn. Using this, it draws data into a canvas and + * then downsamples the characters as necessary for the current display. + * This makes rendering more efficient, rather than drawing a full (tiny) + * font, or downsampling in real-time. + */ +export class MinimapCharRendererFactory { + private static lastCreated?: MinimapCharRenderer; + private static lastFontFamily?: string; + + /** + * Creates a new character renderer factory with the given scale. + */ + public static create(scale: number, fontFamily: string) { + // renderers are immutable. By default we'll 'create' a new minimap + // character renderer whenever we switch editors, no need to do extra work. + if (this.lastCreated && scale === this.lastCreated.scale && fontFamily === this.lastFontFamily) { + return this.lastCreated; + } + + const factory = MinimapCharRendererFactory.createFromSampleData( + MinimapCharRendererFactory.createSampleData(fontFamily).data, + scale + ); + this.lastFontFamily = fontFamily; + this.lastCreated = factory; + return factory; + } + + /** + * Creates the font sample data, writing to a canvas. + */ + public static createSampleData(fontFamily: string): ImageData { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d')!; + + canvas.style.height = `${Constants.SAMPLED_CHAR_HEIGHT}px`; + canvas.height = Constants.SAMPLED_CHAR_HEIGHT; + canvas.width = Constants.CHAR_COUNT * Constants.SAMPLED_CHAR_WIDTH; + canvas.style.width = Constants.CHAR_COUNT * Constants.SAMPLED_CHAR_WIDTH + 'px'; + + ctx.fillStyle = '#ffffff'; + ctx.font = `bold ${Constants.SAMPLED_CHAR_HEIGHT}px ${fontFamily}`; + ctx.textBaseline = 'middle'; + + let x = 0; + for (const code of allCharCodes) { + ctx.fillText(String.fromCharCode(code), x, Constants.SAMPLED_CHAR_HEIGHT / 2); + x += Constants.SAMPLED_CHAR_WIDTH; + } + + return ctx.getImageData(0, 0, Constants.CHAR_COUNT * Constants.SAMPLED_CHAR_WIDTH, Constants.SAMPLED_CHAR_HEIGHT); + } + + /** + * Creates a character renderer from the canvas sample data. + */ + public static createFromSampleData(source: Uint8ClampedArray, scale: number): MinimapCharRenderer { + const expectedLength = + Constants.SAMPLED_CHAR_HEIGHT * Constants.SAMPLED_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT * Constants.CHAR_COUNT; + if (source.length !== expectedLength) { + throw new Error('Unexpected source in MinimapCharRenderer'); + } + + let charData = MinimapCharRendererFactory._downsample(source, scale); + return new MinimapCharRenderer(charData, scale); + } + + private static _downsampleChar( + source: Uint8ClampedArray, + sourceOffset: number, + dest: Uint8ClampedArray, + destOffset: number, + scale: number + ): number { + const width = Constants.BASE_CHAR_WIDTH * scale; + const height = Constants.BASE_CHAR_HEIGHT * scale; + + let targetIndex = destOffset; + let brightest = 0; + + // This is essentially an ad-hoc rescaling algorithm. Standard approaches + // like bicubic interpolation are awesome for scaling between image sizes, + // but don't work so well when scaling to very small pixel values, we end + // up with blurry, indistinct forms. + // + // The approach taken here is simply mapping each source pixel to the target + // pixels, and taking the weighted values for all pixels in each, and then + // averaging them out. Finally we apply an intensity boost in _downsample, + // since when scaling to the smallest pixel sizes there's more black space + // which causes characters to be much less distinct. + for (let y = 0; y < height; y++) { + // 1. For this destination pixel, get the source pixels we're sampling + // from (x1, y1) to the next pixel (x2, y2) + const sourceY1 = (y / height) * Constants.SAMPLED_CHAR_HEIGHT; + const sourceY2 = ((y + 1) / height) * Constants.SAMPLED_CHAR_HEIGHT; + + for (let x = 0; x < width; x++) { + const sourceX1 = (x / width) * Constants.SAMPLED_CHAR_WIDTH; + const sourceX2 = ((x + 1) / width) * Constants.SAMPLED_CHAR_WIDTH; + + // 2. Sample all of them, summing them up and weighting them. Similar + // to bilinear interpolation. + let value = 0; + let samples = 0; + for (let sy = sourceY1; sy < sourceY2; sy++) { + const sourceRow = sourceOffset + Math.floor(sy) * Constants.RGBA_SAMPLED_ROW_WIDTH; + const yBalance = 1 - (sy - Math.floor(sy)); + for (let sx = sourceX1; sx < sourceX2; sx++) { + const xBalance = 1 - (sx - Math.floor(sx)); + const sourceIndex = sourceRow + Math.floor(sx) * Constants.RGBA_CHANNELS_CNT; + + const weight = xBalance * yBalance; + samples += weight; + value += ((source[sourceIndex] * source[sourceIndex + 3]) / 255) * weight; + } + } + + const final = value / samples; + brightest = Math.max(brightest, final); + dest[targetIndex++] = final; + } + } + + return brightest; + } + + private static _downsample(data: Uint8ClampedArray, scale: number): Uint8ClampedArray { + const pixelsPerCharacter = Constants.BASE_CHAR_HEIGHT * scale * Constants.BASE_CHAR_WIDTH * scale; + const resultLen = pixelsPerCharacter * Constants.CHAR_COUNT; + const result = new Uint8ClampedArray(resultLen); + + let resultOffset = 0; + let sourceOffset = 0; + let brightest = 0; + for (let charIndex = 0; charIndex < Constants.CHAR_COUNT; charIndex++) { + brightest = Math.max(brightest, this._downsampleChar(data, sourceOffset, result, resultOffset, scale)); + resultOffset += pixelsPerCharacter; + sourceOffset += Constants.SAMPLED_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT; + } + + if (brightest > 0) { + const adjust = 255 / brightest; + for (let i = 0; i < resultLen; i++) { + result[i] *= adjust; + } + } + + return result; + } +} diff --git a/src/vs/editor/browser/viewParts/minimap/minimapCharSheet.ts b/src/vs/editor/browser/viewParts/minimap/minimapCharSheet.ts new file mode 100644 index 0000000000..5ca6c82254 --- /dev/null +++ b/src/vs/editor/browser/viewParts/minimap/minimapCharSheet.ts @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export const enum Constants { + START_CH_CODE = 32, // Space + END_CH_CODE = 126, // Tilde (~) + UNKNOWN_CODE = 65533, // UTF placeholder code + CHAR_COUNT = END_CH_CODE - START_CH_CODE + 2, + + SAMPLED_CHAR_HEIGHT = 16, + SAMPLED_CHAR_WIDTH = 10, + + BASE_CHAR_HEIGHT = 2, + BASE_CHAR_WIDTH = 1, + + RGBA_CHANNELS_CNT = 4, + RGBA_SAMPLED_ROW_WIDTH = RGBA_CHANNELS_CNT * CHAR_COUNT * SAMPLED_CHAR_WIDTH +} + +export const allCharCodes: ReadonlyArray = (() => { + const v: number[] = []; + for (let i = Constants.START_CH_CODE; i <= Constants.END_CH_CODE; i++) { + v.push(i); + } + + v.push(Constants.UNKNOWN_CODE); + return v; +})(); + +export const getCharIndex = (chCode: number) => { + chCode -= Constants.START_CH_CODE; + if (chCode < 0 || chCode > Constants.CHAR_COUNT) { + return Constants.CHAR_COUNT - 1; // unknown symbol + } + + return chCode; +}; diff --git a/src/vs/editor/browser/viewParts/selections/selections.ts b/src/vs/editor/browser/viewParts/selections/selections.ts index 162e90fe19..91c8058cf8 100644 --- a/src/vs/editor/browser/viewParts/selections/selections.ts +++ b/src/vs/editor/browser/viewParts/selections/selections.ts @@ -278,13 +278,17 @@ export class SelectionsOverlay extends DynamicViewOverlay { ); } - private _actualRenderOneSelection(output2: string[], visibleStartLineNumber: number, hasMultipleSelections: boolean, visibleRanges: LineVisibleRangesWithStyle[]): void { - const visibleRangesHaveStyle = (visibleRanges.length > 0 && visibleRanges[0].ranges[0].startStyle); + private _actualRenderOneSelection(output2: [string, string][], visibleStartLineNumber: number, hasMultipleSelections: boolean, visibleRanges: LineVisibleRangesWithStyle[]): void { + if (visibleRanges.length === 0) { + return; + } + + const visibleRangesHaveStyle = !!visibleRanges[0].ranges[0].startStyle; const fullLineHeight = (this._lineHeight).toString(); const reducedLineHeight = (this._lineHeight - 1).toString(); - const firstLineNumber = (visibleRanges.length > 0 ? visibleRanges[0].lineNumber : 0); - const lastLineNumber = (visibleRanges.length > 0 ? visibleRanges[visibleRanges.length - 1].lineNumber : 0); + const firstLineNumber = visibleRanges[0].lineNumber; + const lastLineNumber = visibleRanges[visibleRanges.length - 1].lineNumber; for (let i = 0, len = visibleRanges.length; i < len; i++) { const lineVisibleRanges = visibleRanges[i]; @@ -294,7 +298,8 @@ export class SelectionsOverlay extends DynamicViewOverlay { const lineHeight = hasMultipleSelections ? (lineNumber === lastLineNumber || lineNumber === firstLineNumber ? reducedLineHeight : fullLineHeight) : fullLineHeight; const top = hasMultipleSelections ? (lineNumber === firstLineNumber ? 1 : 0) : 0; - let lineOutput = ''; + let innerCornerOutput = ''; + let restOfSelectionOutput = ''; for (let j = 0, lenJ = lineVisibleRanges.ranges.length; j < lenJ; j++) { const visibleRange = lineVisibleRanges.ranges[j]; @@ -306,7 +311,7 @@ export class SelectionsOverlay extends DynamicViewOverlay { // Reverse rounded corner to the left // First comes the selection (blue layer) - lineOutput += this._createSelectionPiece(top, lineHeight, SelectionsOverlay.SELECTION_CLASS_NAME, visibleRange.left - SelectionsOverlay.ROUNDED_PIECE_WIDTH, SelectionsOverlay.ROUNDED_PIECE_WIDTH); + innerCornerOutput += this._createSelectionPiece(top, lineHeight, SelectionsOverlay.SELECTION_CLASS_NAME, visibleRange.left - SelectionsOverlay.ROUNDED_PIECE_WIDTH, SelectionsOverlay.ROUNDED_PIECE_WIDTH); // Second comes the background (white layer) with inverse border radius let className = SelectionsOverlay.EDITOR_BACKGROUND_CLASS_NAME; @@ -316,13 +321,13 @@ export class SelectionsOverlay extends DynamicViewOverlay { if (startStyle.bottom === CornerStyle.INTERN) { className += ' ' + SelectionsOverlay.SELECTION_BOTTOM_RIGHT; } - lineOutput += this._createSelectionPiece(top, lineHeight, className, visibleRange.left - SelectionsOverlay.ROUNDED_PIECE_WIDTH, SelectionsOverlay.ROUNDED_PIECE_WIDTH); + innerCornerOutput += this._createSelectionPiece(top, lineHeight, className, visibleRange.left - SelectionsOverlay.ROUNDED_PIECE_WIDTH, SelectionsOverlay.ROUNDED_PIECE_WIDTH); } if (endStyle.top === CornerStyle.INTERN || endStyle.bottom === CornerStyle.INTERN) { // Reverse rounded corner to the right // First comes the selection (blue layer) - lineOutput += this._createSelectionPiece(top, lineHeight, SelectionsOverlay.SELECTION_CLASS_NAME, visibleRange.left + visibleRange.width, SelectionsOverlay.ROUNDED_PIECE_WIDTH); + innerCornerOutput += this._createSelectionPiece(top, lineHeight, SelectionsOverlay.SELECTION_CLASS_NAME, visibleRange.left + visibleRange.width, SelectionsOverlay.ROUNDED_PIECE_WIDTH); // Second comes the background (white layer) with inverse border radius let className = SelectionsOverlay.EDITOR_BACKGROUND_CLASS_NAME; @@ -332,7 +337,7 @@ export class SelectionsOverlay extends DynamicViewOverlay { if (endStyle.bottom === CornerStyle.INTERN) { className += ' ' + SelectionsOverlay.SELECTION_BOTTOM_LEFT; } - lineOutput += this._createSelectionPiece(top, lineHeight, className, visibleRange.left + visibleRange.width, SelectionsOverlay.ROUNDED_PIECE_WIDTH); + innerCornerOutput += this._createSelectionPiece(top, lineHeight, className, visibleRange.left + visibleRange.width, SelectionsOverlay.ROUNDED_PIECE_WIDTH); } } @@ -353,22 +358,26 @@ export class SelectionsOverlay extends DynamicViewOverlay { className += ' ' + SelectionsOverlay.SELECTION_BOTTOM_RIGHT; } } - lineOutput += this._createSelectionPiece(top, lineHeight, className, visibleRange.left, visibleRange.width); + restOfSelectionOutput += this._createSelectionPiece(top, lineHeight, className, visibleRange.left, visibleRange.width); } - output2[lineIndex] += lineOutput; + output2[lineIndex][0] += innerCornerOutput; + output2[lineIndex][1] += restOfSelectionOutput; } } private _previousFrameVisibleRangesWithStyle: (LineVisibleRangesWithStyle[] | null)[] = []; public prepareRender(ctx: RenderingContext): void { - const output: string[] = []; + // Build HTML for inner corners separate from HTML for the the rest of selections, + // as the inner corner HTML can interfere with that of other selections. + // In final render, make sure to place the inner corner HTML before the rest of selection HTML. See issue #77777. + const output: [string, string][] = []; const visibleStartLineNumber = ctx.visibleRange.startLineNumber; const visibleEndLineNumber = ctx.visibleRange.endLineNumber; for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) { const lineIndex = lineNumber - visibleStartLineNumber; - output[lineIndex] = ''; + output[lineIndex] = ['', '']; } const thisFrameVisibleRangesWithStyle: (LineVisibleRangesWithStyle[] | null)[] = []; @@ -385,7 +394,7 @@ export class SelectionsOverlay extends DynamicViewOverlay { } this._previousFrameVisibleRangesWithStyle = thisFrameVisibleRangesWithStyle; - this._renderResult = output; + this._renderResult = output.map(([internalCorners, restOfSelection]) => internalCorners + restOfSelection); } public render(startLineNumber: number, lineNumber: number): string { diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts index 1c38dcc7e7..7c88cd8384 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts @@ -18,7 +18,7 @@ import { registerThemingParticipant } from 'vs/platform/theme/common/themeServic export class ViewCursors extends ViewPart { - static BLINK_INTERVAL = 500; + static readonly BLINK_INTERVAL = 500; private _readOnly: boolean; private _cursorBlinking: TextEditorCursorBlinkingStyle; diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 7b0227d5ab..92f0985852 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -18,7 +18,7 @@ import { Schemas } from 'vs/base/common/network'; import { Configuration } from 'vs/editor/browser/config/configuration'; import { CoreEditorCommand } from 'vs/editor/browser/controller/coreCommands'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; -import { EditorExtensionsRegistry, IEditorContributionCtor } from 'vs/editor/browser/editorExtensions'; +import { EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ICommandDelegate } from 'vs/editor/browser/view/viewController'; import { IContentWidgetData, IOverlayWidgetData, View } from 'vs/editor/browser/view/viewImpl'; @@ -67,7 +67,7 @@ export interface ICodeEditorWidgetOptions { * Contributions to instantiate. * Defaults to EditorExtensionsRegistry.getEditorContributions(). */ - contributions?: IEditorContributionCtor[]; + contributions?: IEditorContributionDescription[]; /** * Telemetry data associated with this CodeEditorWidget. @@ -294,17 +294,16 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._contentWidgets = {}; this._overlayWidgets = {}; - let contributions: IEditorContributionCtor[]; + let contributions: IEditorContributionDescription[]; if (Array.isArray(codeEditorWidgetOptions.contributions)) { contributions = codeEditorWidgetOptions.contributions; } else { contributions = EditorExtensionsRegistry.getEditorContributions(); } - for (let i = 0, len = contributions.length; i < len; i++) { - const ctor = contributions[i]; + for (const desc of contributions) { try { - const contribution = this._instantiationService.createInstance(ctor, this); - this._contributions[contribution.getId()] = contribution; + const contribution = this._instantiationService.createInstance(desc.ctor, this); + this._contributions[desc.id] = contribution; } catch (err) { onUnexpectedError(err); } @@ -500,6 +499,17 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return CursorColumns.visibleColumnFromColumn(this._modelData.model.getLineContent(position.lineNumber), position.column, tabSize) + 1; } + public getStatusbarColumn(rawPosition: IPosition): number { + if (!this._modelData) { + return rawPosition.column; + } + + const position = this._modelData.model.validatePosition(rawPosition); + const tabSize = this._modelData.model.getOptions().tabSize; + + return CursorColumns.toStatusbarColumn(this._modelData.model.getLineContent(position.lineNumber), position.column, tabSize); + } + public getPosition(): Position | null { if (!this._modelData) { return null; diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 3ac66cad5b..5db54a7631 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -20,7 +20,7 @@ import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { DiffReview } from 'vs/editor/browser/widget/diffReview'; -import { IDiffEditorOptions, IEditorOptions, EditorLayoutInfo, IComputedEditorOptions, EditorOption, EditorOptions } from 'vs/editor/common/config/editorOptions'; +import { IDiffEditorOptions, IEditorOptions, EditorLayoutInfo, IComputedEditorOptions, EditorOption, EditorOptions, EditorFontLigatures } from 'vs/editor/common/config/editorOptions'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ISelection, Selection } from 'vs/editor/common/core/selection'; @@ -44,6 +44,10 @@ import { reverseLineChanges } from 'sql/editor/browser/diffEditorHelper'; // {{S import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IDiffLinesChange, InlineDiffMargin } from 'vs/editor/browser/widget/inlineDiffMargin'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { Constants } from 'vs/base/common/uint'; +import { EditorExtensionsRegistry, IDiffEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { IEditorProgressService, IProgressRunner } from 'vs/platform/progress/common/progress'; interface IEditorDiffDecorations { decorations: IModelDeltaDecoration[]; @@ -166,6 +170,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE public readonly onDidUpdateDiff: Event = this._onDidUpdateDiff.event; private readonly id: number; + private _state: editorBrowser.DiffEditorState; + private _updatingDiffProgress: IProgressRunner | null; private readonly _domElement: HTMLElement; protected readonly _containerDomElement: HTMLElement; @@ -199,6 +205,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE private _originalIsEditable: boolean; private _renderSideBySide: boolean; + private _maxComputationTime: number; private _renderIndicators: boolean; private _enableSplitViewResizing: boolean; private _strategy!: IDiffEditorWidgetStyle; @@ -225,6 +232,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE @IThemeService themeService: IThemeService, @INotificationService notificationService: INotificationService, @IContextMenuService contextMenuService: IContextMenuService, + @IEditorProgressService private readonly _editorProgressService: IEditorProgressService ) { super(); @@ -238,6 +246,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._options = options; // {{SQL CARBON EDIT}} this.id = (++DIFF_EDITOR_ID); + this._state = editorBrowser.DiffEditorState.Idle; + this._updatingDiffProgress = null; this._domElement = domElement; options = options || {}; @@ -248,6 +258,12 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._renderSideBySide = options.renderSideBySide; } + // maxComputationTime + this._maxComputationTime = 5000; + if (typeof options.maxComputationTime !== 'undefined') { + this._maxComputationTime = options.maxComputationTime; + } + // ignoreTrimWhitespace this._ignoreTrimWhitespace = true; if (typeof options.ignoreTrimWhitespace !== 'undefined') { @@ -369,6 +385,15 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._containerDomElement.className = DiffEditorWidget._getClassName(this._themeService.getTheme(), this._renderSideBySide); })); + const contributions: IDiffEditorContributionDescription[] = EditorExtensionsRegistry.getDiffEditorContributions(); + for (const desc of contributions) { + try { + this._register(instantiationService.createInstance(desc.ctor, this)); + } catch (err) { + onUnexpectedError(err); + } + } + this._codeEditorService.addDiffEditor(this); } @@ -380,10 +405,30 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE return this._renderSideBySide; } + public get maxComputationTime(): number { + return this._maxComputationTime; + } + public get renderIndicators(): boolean { return this._renderIndicators; } + private _setState(newState: editorBrowser.DiffEditorState): void { + if (this._state === newState) { + return; + } + this._state = newState; + + if (this._updatingDiffProgress) { + this._updatingDiffProgress.done(); + this._updatingDiffProgress = null; + } + + if (this._state === editorBrowser.DiffEditorState.ComputingDiff) { + this._updatingDiffProgress = this._editorProgressService.show(true, 1000); + } + } + public hasWidgetFocus(): boolean { return dom.isAncestor(document.activeElement, this._domElement); } @@ -496,6 +541,12 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } })); + this._register(editor.onDidChangeModelOptions((e) => { + if (e.tabSize) { + this._updateDecorationsRunner.schedule(); + } + })); + return editor; } @@ -565,6 +616,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE return this._diffComputationResult.changes; } + public getDiffComputationResult(): IDiffComputationResult | null { + return this._diffComputationResult; + } + public getOriginalEditor(): editorBrowser.ICodeEditor { return this.originalEditor; } @@ -584,6 +639,13 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } } + if (typeof newOptions.maxComputationTime !== 'undefined') { + this._maxComputationTime = newOptions.maxComputationTime; + if (this._isVisible) { + this._beginUpdateDecorationsSoon(); + } + } + let beginUpdateDecorations = false; if (typeof newOptions.ignoreTrimWhitespace !== 'undefined') { @@ -651,6 +713,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this.modifiedEditor.setModel(model ? model.modified : null); this._updateDecorationsRunner.cancel(); + // this.originalEditor.onDidChangeModelOptions + if (model) { this.originalEditor.setScrollTop(0); this.modifiedEditor.setScrollTop(0); @@ -659,14 +723,13 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE // Disable any diff computations that will come in this._diffComputationResult = null; this._diffComputationToken++; + this._setState(editorBrowser.DiffEditorState.Idle); if (model) { this._recreateOverviewRulers(); // Begin comparing this._beginUpdateDecorations(); - } else { - this._diffComputationResult = null; } this._layoutOverviewViewport(); @@ -680,6 +743,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE return this.modifiedEditor.getVisibleColumnFromPosition(position); } + public getStatusbarColumn(position: IPosition): number { + return this.modifiedEditor.getStatusbarColumn(position); + } + public getPosition(): Position | null { return this.modifiedEditor.getPosition(); } @@ -914,6 +981,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE // yet supported, so using tokens for now. this._diffComputationToken++; let currentToken = this._diffComputationToken; + this._setState(editorBrowser.DiffEditorState.ComputingDiff); if (!this._editorWorkerService.canComputeDiff(currentOriginalModel.uri, currentModifiedModel.uri)) { if ( @@ -927,11 +995,12 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE return; } - this._editorWorkerService.computeDiff(currentOriginalModel.uri, currentModifiedModel.uri, this._ignoreTrimWhitespace).then((result) => { + this._editorWorkerService.computeDiff(currentOriginalModel.uri, currentModifiedModel.uri, this._ignoreTrimWhitespace, this._maxComputationTime).then((result) => { if (currentToken === this._diffComputationToken && currentOriginalModel === this.originalEditor.getModel() && currentModifiedModel === this.modifiedEditor.getModel() ) { + this._setState(editorBrowser.DiffEditorState.DiffComputed); this._diffComputationResult = result; this._updateDecorationsRunner.schedule(); this._onDidUpdateDiff.fire(); @@ -941,6 +1010,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE && currentOriginalModel === this.originalEditor.getModel() && currentModifiedModel === this.modifiedEditor.getModel() ) { + this._setState(editorBrowser.DiffEditorState.DiffComputed); this._diffComputationResult = null; this._updateDecorationsRunner.schedule(); } @@ -995,7 +1065,6 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE private _adjustOptionsForLeftHandSide(options: IDiffEditorOptions, isEditable: boolean): IEditorOptions { let result = this._adjustOptionsForSubEditor(options); result.readOnly = !isEditable; - result.overviewRulerLanes = 1; result.extraEditorClassName = 'original-in-monaco-diff-editor'; return result; } @@ -1128,7 +1197,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE while (min < max) { let mid = Math.floor((min + max) / 2); let midStart = startLineNumberExtractor(lineChanges[mid]); - let midEnd = (mid + 1 <= max ? startLineNumberExtractor(lineChanges[mid + 1]) : Number.MAX_VALUE); + let midEnd = (mid + 1 <= max ? startLineNumberExtractor(lineChanges[mid + 1]) : Constants.MAX_SAFE_SMALL_INTEGER); if (lineNumber < midStart) { max = mid - 1; @@ -1580,7 +1649,7 @@ const DECORATIONS = { class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffEditorWidgetStyle, IVerticalSashLayoutProvider { - static MINIMUM_EDITOR_WIDTH = 100; + static readonly MINIMUM_EDITOR_WIDTH = 100; private _disableSash: boolean; private readonly _sash: Sash; @@ -1700,11 +1769,11 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffE if (isChangeOrDelete(lineChange)) { result.decorations.push({ - range: new Range(lineChange.originalStartLineNumber, 1, lineChange.originalEndLineNumber, Number.MAX_VALUE), + range: new Range(lineChange.originalStartLineNumber, 1, lineChange.originalEndLineNumber, Constants.MAX_SAFE_SMALL_INTEGER), options: (renderIndicators ? DECORATIONS.lineDeleteWithSign : DECORATIONS.lineDelete) }); if (!isChangeOrInsert(lineChange) || !lineChange.charChanges) { - result.decorations.push(createDecoration(lineChange.originalStartLineNumber, 1, lineChange.originalEndLineNumber, Number.MAX_VALUE, DECORATIONS.charDeleteWholeLine)); + result.decorations.push(createDecoration(lineChange.originalStartLineNumber, 1, lineChange.originalEndLineNumber, Constants.MAX_SAFE_SMALL_INTEGER, DECORATIONS.charDeleteWholeLine)); } result.overviewZones.push(new OverviewRulerZone( @@ -1761,11 +1830,11 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffE if (isChangeOrInsert(lineChange)) { result.decorations.push({ - range: new Range(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedEndLineNumber, Number.MAX_VALUE), + range: new Range(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedEndLineNumber, Constants.MAX_SAFE_SMALL_INTEGER), options: (renderIndicators ? DECORATIONS.lineInsertWithSign : DECORATIONS.lineInsert) }); if (!isChangeOrDelete(lineChange) || !lineChange.charChanges) { - result.decorations.push(createDecoration(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedEndLineNumber, Number.MAX_VALUE, DECORATIONS.charInsertWholeLine)); + result.decorations.push(createDecoration(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedEndLineNumber, Constants.MAX_SAFE_SMALL_INTEGER, DECORATIONS.charInsertWholeLine)); } result.overviewZones.push(new OverviewRulerZone( lineChange.modifiedStartLineNumber, @@ -1879,7 +1948,7 @@ class DiffEditorWidgetInline extends DiffEditorWidgetStyle implements IDiffEdito // Add overview zones in the overview ruler if (isChangeOrDelete(lineChange)) { result.decorations.push({ - range: new Range(lineChange.originalStartLineNumber, 1, lineChange.originalEndLineNumber, Number.MAX_VALUE), + range: new Range(lineChange.originalStartLineNumber, 1, lineChange.originalEndLineNumber, Constants.MAX_SAFE_SMALL_INTEGER), options: DECORATIONS.lineDeleteMargin }); @@ -1910,7 +1979,7 @@ class DiffEditorWidgetInline extends DiffEditorWidgetStyle implements IDiffEdito // Add decorations & overview zones if (isChangeOrInsert(lineChange)) { result.decorations.push({ - range: new Range(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedEndLineNumber, Number.MAX_VALUE), + range: new Range(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedEndLineNumber, Constants.MAX_SAFE_SMALL_INTEGER), options: (renderIndicators ? DECORATIONS.lineInsertWithSign : DECORATIONS.lineInsert) }); @@ -1946,7 +2015,7 @@ class DiffEditorWidgetInline extends DiffEditorWidgetStyle implements IDiffEdito } } } else { - result.decorations.push(createDecoration(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedEndLineNumber, Number.MAX_VALUE, DECORATIONS.charInsertWholeLine)); + result.decorations.push(createDecoration(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedEndLineNumber, Constants.MAX_SAFE_SMALL_INTEGER, DECORATIONS.charInsertWholeLine)); } } } @@ -2078,7 +2147,7 @@ class InlineViewZonesComputer extends ViewZonesComputer { const isBasicASCII = ViewLineRenderingData.isBasicASCII(lineContent, originalModel.mightContainNonBasicASCII()); const containsRTL = ViewLineRenderingData.containsRTL(lineContent, isBasicASCII, originalModel.mightContainRTL()); const output = renderViewLine(new RenderLineInput( - (fontInfo.isMonospace && !options.get(EditorOption.disableMonospaceOptimizations) && !options.get(EditorOption.fontLigatures)), + (fontInfo.isMonospace && !options.get(EditorOption.disableMonospaceOptimizations)), fontInfo.canUseHalfwidthRightwardsArrow, lineContent, false, @@ -2092,7 +2161,7 @@ class InlineViewZonesComputer extends ViewZonesComputer { options.get(EditorOption.stopRenderingLineAfter), options.get(EditorOption.renderWhitespace), options.get(EditorOption.renderControlCharacters), - options.get(EditorOption.fontLigatures), + options.get(EditorOption.fontLigatures) !== EditorFontLigatures.OFF, null // Send no selections, original line cannot be selected ), sb); diff --git a/src/vs/editor/browser/widget/diffReview.ts b/src/vs/editor/browser/widget/diffReview.ts index b175fd1daf..a3082b9cde 100644 --- a/src/vs/editor/browser/widget/diffReview.ts +++ b/src/vs/editor/browser/widget/diffReview.ts @@ -17,7 +17,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; -import { IComputedEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions'; +import { IComputedEditorOptions, EditorOption, EditorFontLigatures } from 'vs/editor/common/config/editorOptions'; import { LineTokens } from 'vs/editor/common/core/lineTokens'; import { Position } from 'vs/editor/common/core/position'; import { ILineChange, ScrollType } from 'vs/editor/common/editorCommon'; @@ -770,7 +770,7 @@ export class DiffReview extends Disposable { const isBasicASCII = ViewLineRenderingData.isBasicASCII(lineContent, model.mightContainNonBasicASCII()); const containsRTL = ViewLineRenderingData.containsRTL(lineContent, isBasicASCII, model.mightContainRTL()); const r = renderViewLine(new RenderLineInput( - (fontInfo.isMonospace && !options.get(EditorOption.disableMonospaceOptimizations) && !options.get(EditorOption.fontLigatures)), + (fontInfo.isMonospace && !options.get(EditorOption.disableMonospaceOptimizations)), fontInfo.canUseHalfwidthRightwardsArrow, lineContent, false, @@ -784,7 +784,7 @@ export class DiffReview extends Disposable { options.get(EditorOption.stopRenderingLineAfter), options.get(EditorOption.renderWhitespace), options.get(EditorOption.renderControlCharacters), - options.get(EditorOption.fontLigatures), + options.get(EditorOption.fontLigatures) !== EditorFontLigatures.OFF, null )); diff --git a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts index 9affe4c81c..bec6408b69 100644 --- a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts +++ b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts @@ -18,6 +18,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IEditorProgressService } from 'vs/platform/progress/common/progress'; export class EmbeddedCodeEditorWidget extends CodeEditorWidget { @@ -78,9 +79,10 @@ export class EmbeddedDiffEditorWidget extends DiffEditorWidget { @IThemeService themeService: IThemeService, @INotificationService notificationService: INotificationService, @IContextMenuService contextMenuService: IContextMenuService, - @IClipboardService clipboardService: IClipboardService + @IClipboardService clipboardService: IClipboardService, + @IEditorProgressService editorProgressService: IEditorProgressService, ) { - super(domElement, parentEditor.getRawOptions(), clipboardService, editorWorkerService, contextKeyService, instantiationService, codeEditorService, themeService, notificationService, contextMenuService); + super(domElement, parentEditor.getRawOptions(), clipboardService, editorWorkerService, contextKeyService, instantiationService, codeEditorService, themeService, notificationService, contextMenuService, editorProgressService); this._parentEditor = parentEditor; this._overwriteOptions = options; diff --git a/src/vs/editor/browser/widget/media/editor.css b/src/vs/editor/browser/widget/media/editor.css index 433b84bf39..6f1b258d67 100644 --- a/src/vs/editor/browser/widget/media/editor.css +++ b/src/vs/editor/browser/widget/media/editor.css @@ -21,12 +21,6 @@ position: relative; overflow: visible; -webkit-text-size-adjust: 100%; - -webkit-font-feature-settings: "liga" off, "calt" off; - font-feature-settings: "liga" off, "calt" off; -} -.monaco-editor.enable-ligatures { - -webkit-font-feature-settings: "liga" on, "calt" on; - font-feature-settings: "liga" on, "calt" on; } /* -------------------- Misc -------------------- */ diff --git a/src/vs/editor/common/commands/replaceCommand.ts b/src/vs/editor/common/commands/replaceCommand.ts index c34bb857b2..3215e3acd9 100644 --- a/src/vs/editor/common/commands/replaceCommand.ts +++ b/src/vs/editor/common/commands/replaceCommand.ts @@ -36,6 +36,27 @@ export class ReplaceCommand implements ICommand { } } +export class ReplaceCommandThatSelectsText implements ICommand { + + private readonly _range: Range; + private readonly _text: string; + + constructor(range: Range, text: string) { + this._range = range; + this._text = text; + } + + public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void { + builder.addTrackedEditOperation(this._range, this._text); + } + + public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { + const inverseEditOperations = helper.getInverseEditOperations(); + const srcRange = inverseEditOperations[0].range; + return new Selection(srcRange.startLineNumber, srcRange.startColumn, srcRange.endLineNumber, srcRange.endColumn); + } +} + export class ReplaceCommandWithoutChangingPosition implements ICommand { private readonly _range: Range; diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 1336ba6f84..cb5d69d5cf 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -207,6 +207,17 @@ function migrateOptions(options: IEditorOptions): void { enabled: false }; } + + const parameterHints = options.parameterHints; + if (parameterHints === true) { + options.parameterHints = { + enabled: true + }; + } else if (parameterHints === false) { + options.parameterHints = { + enabled: false + }; + } } function deepCloneAndMigrateOptions(_options: IEditorOptions): IEditorOptions { @@ -463,6 +474,11 @@ const editorConfiguration: IConfigurationNode = { default: 750, description: nls.localize('codeActionsOnSaveTimeout', "Timeout in milliseconds after which the code actions that are run on save are cancelled.") }, + 'diffEditor.maxComputationTime': { + type: 'number', + default: 5000, + description: nls.localize('maxComputationTime', "Timeout in milliseconds after which diff computation is cancelled. Use 0 for no timeout.") + }, 'diffEditor.renderSideBySide': { type: 'boolean', default: true, diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 1d2d536fa1..a7d8622ea7 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import * as platform from 'vs/base/common/platform'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { FontInfo } from 'vs/editor/common/config/fontInfo'; -import { Constants } from 'vs/editor/common/core/uint'; +import { Constants } from 'vs/base/common/uint'; import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/model/wordHelper'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { isObject } from 'vs/base/common/types'; @@ -178,7 +178,7 @@ export interface IEditorOptions { * Enable font ligatures. * Defaults to false. */ - fontLigatures?: boolean; + fontLigatures?: boolean | string; /** * Disable the use of `will-change` for the editor margin and lines layers. * The usage of `will-change` acts as a hint for browsers to create an extra layer. @@ -538,6 +538,11 @@ export interface IDiffEditorOptions extends IEditorOptions { * Defaults to true. */ renderSideBySide?: boolean; + /** + * Timeout in milliseconds after which diff computation is cancelled. + * Defaults to 5000. + */ + maxComputationTime?: number; /** * Compute the diff by ignoring leading/trailing whitespace * Defaults to true. @@ -647,6 +652,9 @@ export interface IEditorOption { type PossibleKeyName0 = { [K in keyof IEditorOptions]: IEditorOptions[K] extends V | undefined ? K : never }[keyof IEditorOptions]; type PossibleKeyName = NonNullable>; +/** + * @internal + */ abstract class BaseEditorOption implements IEditorOption { public readonly id: K1; @@ -1050,7 +1058,7 @@ function _cursorStyleFromString(cursorStyle: 'line' | 'block' | 'underline' | 'l class EditorClassName extends ComputedEditorOption { constructor() { - super(EditorOption.editorClassName, [EditorOption.mouseStyle, EditorOption.fontLigatures, EditorOption.extraEditorClassName]); + super(EditorOption.editorClassName, [EditorOption.mouseStyle, EditorOption.extraEditorClassName]); } public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, _: string): string { @@ -1061,9 +1069,6 @@ class EditorClassName extends ComputedEditorOption //#endregion +//#region fontLigatures + +/** + * @internal + */ +export class EditorFontLigatures extends BaseEditorOption { + + public static OFF = '"liga" off, "calt" off'; + public static ON = '"liga" on, "calt" on'; + + constructor() { + super( + EditorOption.fontLigatures, 'fontLigatures', EditorFontLigatures.OFF, + { + anyOf: [ + { + type: 'boolean', + description: nls.localize('fontLigatures', "Enables/Disables font ligatures."), + }, + { + type: 'string', + description: nls.localize('fontFeatureSettings', "Explicit font-feature-settings.") + } + ], + default: false + } + ); + } + + public validate(input: any): string { + if (typeof input === 'undefined') { + return this.defaultValue; + } + if (typeof input === 'string') { + if (input === 'false') { + return EditorFontLigatures.OFF; + } + if (input === 'true') { + return EditorFontLigatures.ON; + } + return input; + } + if (Boolean(input)) { + return EditorFontLigatures.ON; + } + return EditorFontLigatures.OFF; + } +} + +//#endregion + //#region fontInfo class EditorFontInfo extends ComputedEditorOption { @@ -1364,10 +1420,8 @@ export interface OverviewRulerPosition { export const enum RenderMinimap { None = 0, - Small = 1, - Large = 2, - SmallBlocks = 3, - LargeBlocks = 4, + Text = 1, + Blocks = 2, } /** @@ -1523,6 +1577,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption= 2 ? Math.round(minimap.scale * 2) : minimap.scale); const minimapMaxColumn = minimap.maxColumn | 0; const scrollbar = options.get(EditorOption.scrollbar); @@ -1573,14 +1628,10 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption= 2) { - renderMinimap = minimapRenderCharacters ? RenderMinimap.Large : RenderMinimap.LargeBlocks; - minimapCharWidth = 2 / pixelRatio; - } else { - renderMinimap = minimapRenderCharacters ? RenderMinimap.Small : RenderMinimap.SmallBlocks; - minimapCharWidth = 1 / pixelRatio; - } + // The minimapScale is also the pixel width of each character. Adjust + // for the pixel ratio of the screen. + const minimapCharWidth = minimapScale / pixelRatio; + renderMinimap = minimapRenderCharacters ? RenderMinimap.Text : RenderMinimap.Blocks; // Given: // (leaving 2px for the cursor to have space after the last character) @@ -1756,6 +1807,11 @@ export interface IEditorMinimapOptions { * Defaults to 120. */ maxColumn?: number; + + /** + * Relative size of the font in the minimap. Defaults to 1. + */ + scale?: number; } export type EditorMinimapOptions = Readonly>; @@ -1769,6 +1825,7 @@ class EditorMinimap extends BaseEditorOption(input.side, this.defaultValue.side, ['right', 'left']), showSlider: EditorStringEnumOption.stringSet<'always' | 'mouseover'>(input.showSlider, this.defaultValue.showSlider, ['always', 'mouseover']), renderCharacters: EditorBooleanOption.boolean(input.renderCharacters, this.defaultValue.renderCharacters), + scale: EditorIntOption.clampedInt(input.scale, 1, 1, 3), maxColumn: EditorIntOption.clampedInt(input.maxColumn, this.defaultValue.maxColumn, 1, 10000), }; } @@ -1960,6 +2025,7 @@ class EditorQuickSuggestions extends BaseEditorOption = this._register(new Emitter()); public readonly onDidReachMaxCursorCount: Event = this._onDidReachMaxCursorCount.event; diff --git a/src/vs/editor/common/controller/cursorCommon.ts b/src/vs/editor/common/controller/cursorCommon.ts index 1acd5d9d71..b900ebb780 100644 --- a/src/vs/editor/common/controller/cursorCommon.ts +++ b/src/vs/editor/common/controller/cursorCommon.ts @@ -549,6 +549,29 @@ export class CursorColumns { return result; } + public static toStatusbarColumn(lineContent: string, column: number, tabSize: number): number { + let endOffset = lineContent.length; + if (endOffset > column - 1) { + endOffset = column - 1; + } + + let result = 0; + for (let i = 0; i < endOffset; i++) { + let charCode = lineContent.charCodeAt(i); + if (charCode === CharCode.Tab) { + result = this.nextRenderTabStop(result, tabSize); + } else { + if (strings.isHighSurrogate(charCode)) { + result = result + 1; + i = i + 1; + } else { + result = result + 1; + } + } + } + return result + 1; + } + public static visibleColumnFromColumn2(config: CursorConfiguration, model: ICursorSimpleModel, position: Position): number { return this.visibleColumnFromColumn(model.getLineContent(position.lineNumber), position.column, config.tabSize); } diff --git a/src/vs/editor/common/controller/cursorMoveCommands.ts b/src/vs/editor/common/controller/cursorMoveCommands.ts index f4c04d7e77..ec4a668e70 100644 --- a/src/vs/editor/common/controller/cursorMoveCommands.ts +++ b/src/vs/editor/common/controller/cursorMoveCommands.ts @@ -122,20 +122,19 @@ export class CursorMoveCommands { for (let i = 0, len = cursors.length; i < len; i++) { const cursor = cursors[i]; - const viewSelection = cursor.viewState.selection; - const startLineNumber = viewSelection.startLineNumber; - const lineCount = context.viewModel.getLineCount(); + const startLineNumber = cursor.modelState.selection.startLineNumber; + const lineCount = context.model.getLineCount(); - let endLineNumber = viewSelection.endLineNumber; + let endLineNumber = cursor.modelState.selection.endLineNumber; let endColumn: number; if (endLineNumber === lineCount) { - endColumn = context.viewModel.getLineMaxColumn(lineCount); + endColumn = context.model.getLineMaxColumn(lineCount); } else { endLineNumber++; endColumn = 1; } - result[i] = CursorState.fromViewState(new SingleCursorState( + result[i] = CursorState.fromModelState(new SingleCursorState( new Range(startLineNumber, 1, startLineNumber, 1), 0, new Position(endLineNumber, endColumn), 0 )); diff --git a/src/vs/editor/common/controller/cursorTypeOperations.ts b/src/vs/editor/common/controller/cursorTypeOperations.ts index 562be37c99..b3e77ffc2e 100644 --- a/src/vs/editor/common/controller/cursorTypeOperations.ts +++ b/src/vs/editor/common/controller/cursorTypeOperations.ts @@ -6,7 +6,7 @@ import { CharCode } from 'vs/base/common/charCode'; import { onUnexpectedError } from 'vs/base/common/errors'; import * as strings from 'vs/base/common/strings'; -import { ReplaceCommand, ReplaceCommandWithOffsetCursorState, ReplaceCommandWithoutChangingPosition } from 'vs/editor/common/commands/replaceCommand'; +import { ReplaceCommand, ReplaceCommandWithOffsetCursorState, ReplaceCommandWithoutChangingPosition, ReplaceCommandThatPreservesSelection } from 'vs/editor/common/commands/replaceCommand'; import { ShiftCommand } from 'vs/editor/common/commands/shiftCommand'; import { SurroundSelectionCommand } from 'vs/editor/common/commands/surroundSelectionCommand'; import { CursorColumns, CursorConfiguration, EditOperationResult, EditOperationType, ICursorSimpleModel, isQuote } from 'vs/editor/common/controller/cursorCommon'; @@ -81,20 +81,17 @@ export class TypeOperations { const selection = selections[i]; let position = selection.getPosition(); + if (pasteOnNewLine && !selection.isEmpty()) { + pasteOnNewLine = false; + } if (pasteOnNewLine && text.indexOf('\n') !== text.length - 1) { pasteOnNewLine = false; } - if (pasteOnNewLine && selection.startLineNumber !== selection.endLineNumber) { - pasteOnNewLine = false; - } - if (pasteOnNewLine && selection.startColumn === model.getLineMinColumn(selection.startLineNumber) && selection.endColumn === model.getLineMaxColumn(selection.startLineNumber)) { - pasteOnNewLine = false; - } if (pasteOnNewLine) { // Paste entire line at the beginning of line let typeSelection = new Range(position.lineNumber, 1, position.lineNumber, 1); - commands[i] = new ReplaceCommand(typeSelection, text); + commands[i] = new ReplaceCommandThatPreservesSelection(typeSelection, text, selection); } else { commands[i] = new ReplaceCommand(selection, text); } @@ -462,6 +459,12 @@ export class TypeOperations { return false; } + // Do not over-type after a backslash + const beforeCharacter = position.column > 2 ? lineText.charCodeAt(position.column - 2) : CharCode.Null; + if (beforeCharacter === CharCode.Backslash) { + return false; + } + // Must over-type a closing character typed by the editor if (config.autoClosingOvertype === 'auto') { let found = false; diff --git a/src/vs/editor/common/core/characterClassifier.ts b/src/vs/editor/common/core/characterClassifier.ts index b2d5cac068..c192a42f28 100644 --- a/src/vs/editor/common/core/characterClassifier.ts +++ b/src/vs/editor/common/core/characterClassifier.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { toUint8 } from 'vs/editor/common/core/uint'; +import { toUint8 } from 'vs/base/common/uint'; /** * A fast character classifier that uses a compact array for ASCII values. diff --git a/src/vs/editor/common/core/rgba.ts b/src/vs/editor/common/core/rgba.ts index 68b5a65091..32e92621de 100644 --- a/src/vs/editor/common/core/rgba.ts +++ b/src/vs/editor/common/core/rgba.ts @@ -10,7 +10,7 @@ export class RGBA8 { _rgba8Brand: void; - static Empty = new RGBA8(0, 0, 0, 0); + static readonly Empty = new RGBA8(0, 0, 0, 0); /** * Red: integer in [0-255] diff --git a/src/vs/editor/common/core/selection.ts b/src/vs/editor/common/core/selection.ts index 55ef09974f..3461191cb3 100644 --- a/src/vs/editor/common/core/selection.ts +++ b/src/vs/editor/common/core/selection.ts @@ -73,13 +73,6 @@ export class Selection extends Range { this.positionColumn = positionColumn; } - /** - * Clone this selection. - */ - public clone(): Selection { - return new Selection(this.selectionStartLineNumber, this.selectionStartColumn, this.positionLineNumber, this.positionColumn); - } - /** * Transform to a human-readable representation. */ diff --git a/src/vs/editor/common/diff/diffComputer.ts b/src/vs/editor/common/diff/diffComputer.ts index 9d606a1e90..1ac3802bfc 100644 --- a/src/vs/editor/common/diff/diffComputer.ts +++ b/src/vs/editor/common/diff/diffComputer.ts @@ -3,83 +3,63 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDiffChange, ISequence, LcsDiff } from 'vs/base/common/diff/diff'; +import { IDiffChange, ISequence, LcsDiff, IDiffResult } from 'vs/base/common/diff/diff'; import * as strings from 'vs/base/common/strings'; import { ICharChange, ILineChange } from 'vs/editor/common/editorCommon'; -const MAXIMUM_RUN_TIME = 5000; // 5 seconds const MINIMUM_MATCHING_CHARACTER_LENGTH = 3; -function computeDiff(originalSequence: ISequence, modifiedSequence: ISequence, continueProcessingPredicate: () => boolean, pretty: boolean): IDiffChange[] { +export interface IDiffComputerResult { + quitEarly: boolean; + changes: ILineChange[]; +} + +function computeDiff(originalSequence: ISequence, modifiedSequence: ISequence, continueProcessingPredicate: () => boolean, pretty: boolean): IDiffResult { const diffAlgo = new LcsDiff(originalSequence, modifiedSequence, continueProcessingPredicate); return diffAlgo.ComputeDiff(pretty); } -class LineMarkerSequence implements ISequence { +class LineSequence implements ISequence { - private readonly _lines: string[]; + public readonly lines: string[]; private readonly _startColumns: number[]; private readonly _endColumns: number[]; constructor(lines: string[]) { - let startColumns: number[] = []; - let endColumns: number[] = []; + const startColumns: number[] = []; + const endColumns: number[] = []; for (let i = 0, length = lines.length; i < length; i++) { - startColumns[i] = LineMarkerSequence._getFirstNonBlankColumn(lines[i], 1); - endColumns[i] = LineMarkerSequence._getLastNonBlankColumn(lines[i], 1); + startColumns[i] = getFirstNonBlankColumn(lines[i], 1); + endColumns[i] = getLastNonBlankColumn(lines[i], 1); } - this._lines = lines; + this.lines = lines; this._startColumns = startColumns; this._endColumns = endColumns; } - public getLength(): number { - return this._lines.length; - } - - public getElementAtIndex(i: number): string { - return this._lines[i].substring(this._startColumns[i] - 1, this._endColumns[i] - 1); + public getElements(): Int32Array | number[] | string[] { + const elements: string[] = []; + for (let i = 0, len = this.lines.length; i < len; i++) { + elements[i] = this.lines[i].substring(this._startColumns[i] - 1, this._endColumns[i] - 1); + } + return elements; } public getStartLineNumber(i: number): number { return i + 1; } - public getStartColumn(i: number): number { - return this._startColumns[i]; - } - public getEndLineNumber(i: number): number { return i + 1; } - public getEndColumn(i: number): number { - return this._endColumns[i]; - } - - public static _getFirstNonBlankColumn(txt: string, defaultValue: number): number { - const r = strings.firstNonWhitespaceIndex(txt); - if (r === -1) { - return defaultValue; - } - return r + 1; - } - - public static _getLastNonBlankColumn(txt: string, defaultValue: number): number { - const r = strings.lastNonWhitespaceIndex(txt); - if (r === -1) { - return defaultValue; - } - return r + 2; - } - - public getCharSequence(shouldIgnoreTrimWhitespace: boolean, startIndex: number, endIndex: number): CharSequence { - let charCodes: number[] = []; - let lineNumbers: number[] = []; - let columns: number[] = []; + public createCharSequence(shouldIgnoreTrimWhitespace: boolean, startIndex: number, endIndex: number): CharSequence { + const charCodes: number[] = []; + const lineNumbers: number[] = []; + const columns: number[] = []; let len = 0; for (let index = startIndex; index <= endIndex; index++) { - const lineContent = this._lines[index]; + const lineContent = this.lines[index]; const startColumn = (shouldIgnoreTrimWhitespace ? this._startColumns[index] : 1); const endColumn = (shouldIgnoreTrimWhitespace ? this._endColumns[index] : lineContent.length + 1); for (let col = startColumn; col < endColumn; col++) { @@ -105,12 +85,8 @@ class CharSequence implements ISequence { this._columns = columns; } - public getLength(): number { - return this._charCodes.length; - } - - public getElementAtIndex(i: number): number { - return this._charCodes[i]; + public getElements(): Int32Array | number[] | string[] { + return this._charCodes; } public getStartLineNumber(i: number): number { @@ -208,7 +184,7 @@ function postProcessCharChanges(rawChanges: IDiffChange[]): IDiffChange[] { return rawChanges; } - let result = [rawChanges[0]]; + const result = [rawChanges[0]]; let prevChange = result[0]; for (let i = 1, len = rawChanges.length; i < len; i++) { @@ -254,7 +230,7 @@ class LineChange implements ILineChange { this.charChanges = charChanges; } - public static createFromDiffResult(shouldIgnoreTrimWhitespace: boolean, diffChange: IDiffChange, originalLineSequence: LineMarkerSequence, modifiedLineSequence: LineMarkerSequence, continueProcessingPredicate: () => boolean, shouldComputeCharChanges: boolean, shouldPostProcessCharChanges: boolean): LineChange { + public static createFromDiffResult(shouldIgnoreTrimWhitespace: boolean, diffChange: IDiffChange, originalLineSequence: LineSequence, modifiedLineSequence: LineSequence, continueCharDiff: () => boolean, shouldComputeCharChanges: boolean, shouldPostProcessCharChanges: boolean): LineChange { let originalStartLineNumber: number; let originalEndLineNumber: number; let modifiedStartLineNumber: number; @@ -277,11 +253,12 @@ class LineChange implements ILineChange { modifiedEndLineNumber = modifiedLineSequence.getEndLineNumber(diffChange.modifiedStart + diffChange.modifiedLength - 1); } - if (shouldComputeCharChanges && diffChange.originalLength !== 0 && diffChange.modifiedLength !== 0 && continueProcessingPredicate()) { - const originalCharSequence = originalLineSequence.getCharSequence(shouldIgnoreTrimWhitespace, diffChange.originalStart, diffChange.originalStart + diffChange.originalLength - 1); - const modifiedCharSequence = modifiedLineSequence.getCharSequence(shouldIgnoreTrimWhitespace, diffChange.modifiedStart, diffChange.modifiedStart + diffChange.modifiedLength - 1); + if (shouldComputeCharChanges && diffChange.originalLength > 0 && diffChange.originalLength < 20 && diffChange.modifiedLength > 0 && diffChange.modifiedLength < 20 && continueCharDiff()) { + // Compute character changes for diff chunks of at most 20 lines... + const originalCharSequence = originalLineSequence.createCharSequence(shouldIgnoreTrimWhitespace, diffChange.originalStart, diffChange.originalStart + diffChange.originalLength - 1); + const modifiedCharSequence = modifiedLineSequence.createCharSequence(shouldIgnoreTrimWhitespace, diffChange.modifiedStart, diffChange.modifiedStart + diffChange.modifiedLength - 1); - let rawChanges = computeDiff(originalCharSequence, modifiedCharSequence, continueProcessingPredicate, true); + let rawChanges = computeDiff(originalCharSequence, modifiedCharSequence, continueCharDiff, true).changes; if (shouldPostProcessCharChanges) { rawChanges = postProcessCharChanges(rawChanges); @@ -302,6 +279,7 @@ export interface IDiffComputerOpts { shouldPostProcessCharChanges: boolean; shouldIgnoreTrimWhitespace: boolean; shouldMakePrettyDiff: boolean; + maxComputationTime: number; } export class DiffComputer { @@ -310,88 +288,96 @@ export class DiffComputer { private readonly shouldPostProcessCharChanges: boolean; private readonly shouldIgnoreTrimWhitespace: boolean; private readonly shouldMakePrettyDiff: boolean; - private readonly maximumRunTimeMs: number; private readonly originalLines: string[]; private readonly modifiedLines: string[]; - private readonly original: LineMarkerSequence; - private readonly modified: LineMarkerSequence; - - private computationStartTime: number; + private readonly original: LineSequence; + private readonly modified: LineSequence; + private readonly continueLineDiff: () => boolean; + private readonly continueCharDiff: () => boolean; constructor(originalLines: string[], modifiedLines: string[], opts: IDiffComputerOpts) { this.shouldComputeCharChanges = opts.shouldComputeCharChanges; this.shouldPostProcessCharChanges = opts.shouldPostProcessCharChanges; this.shouldIgnoreTrimWhitespace = opts.shouldIgnoreTrimWhitespace; this.shouldMakePrettyDiff = opts.shouldMakePrettyDiff; - this.maximumRunTimeMs = MAXIMUM_RUN_TIME; this.originalLines = originalLines; this.modifiedLines = modifiedLines; - this.original = new LineMarkerSequence(originalLines); - this.modified = new LineMarkerSequence(modifiedLines); + this.original = new LineSequence(originalLines); + this.modified = new LineSequence(modifiedLines); - this.computationStartTime = (new Date()).getTime(); + this.continueLineDiff = createContinueProcessingPredicate(opts.maxComputationTime); + this.continueCharDiff = createContinueProcessingPredicate(opts.maxComputationTime === 0 ? 0 : Math.min(opts.maxComputationTime, 5000)); // never run after 5s for character changes... } - public computeDiff(): ILineChange[] { + public computeDiff(): IDiffComputerResult { - if (this.original.getLength() === 1 && this.original.getElementAtIndex(0).length === 0) { + if (this.original.lines.length === 1 && this.original.lines[0].length === 0) { // empty original => fast path - return [{ - originalStartLineNumber: 1, - originalEndLineNumber: 1, - modifiedStartLineNumber: 1, - modifiedEndLineNumber: this.modified.getLength(), - charChanges: [{ - modifiedEndColumn: 0, - modifiedEndLineNumber: 0, - modifiedStartColumn: 0, - modifiedStartLineNumber: 0, - originalEndColumn: 0, - originalEndLineNumber: 0, - originalStartColumn: 0, - originalStartLineNumber: 0 + return { + quitEarly: false, + changes: [{ + originalStartLineNumber: 1, + originalEndLineNumber: 1, + modifiedStartLineNumber: 1, + modifiedEndLineNumber: this.modified.lines.length, + charChanges: [{ + modifiedEndColumn: 0, + modifiedEndLineNumber: 0, + modifiedStartColumn: 0, + modifiedStartLineNumber: 0, + originalEndColumn: 0, + originalEndLineNumber: 0, + originalStartColumn: 0, + originalStartLineNumber: 0 + }] }] - }]; + }; } - if (this.modified.getLength() === 1 && this.modified.getElementAtIndex(0).length === 0) { + if (this.modified.lines.length === 1 && this.modified.lines[0].length === 0) { // empty modified => fast path - return [{ - originalStartLineNumber: 1, - originalEndLineNumber: this.original.getLength(), - modifiedStartLineNumber: 1, - modifiedEndLineNumber: 1, - charChanges: [{ - modifiedEndColumn: 0, - modifiedEndLineNumber: 0, - modifiedStartColumn: 0, - modifiedStartLineNumber: 0, - originalEndColumn: 0, - originalEndLineNumber: 0, - originalStartColumn: 0, - originalStartLineNumber: 0 + return { + quitEarly: false, + changes: [{ + originalStartLineNumber: 1, + originalEndLineNumber: this.original.lines.length, + modifiedStartLineNumber: 1, + modifiedEndLineNumber: 1, + charChanges: [{ + modifiedEndColumn: 0, + modifiedEndLineNumber: 0, + modifiedStartColumn: 0, + modifiedStartLineNumber: 0, + originalEndColumn: 0, + originalEndLineNumber: 0, + originalStartColumn: 0, + originalStartLineNumber: 0 + }] }] - }]; + }; } - this.computationStartTime = (new Date()).getTime(); - - let rawChanges = computeDiff(this.original, this.modified, this._continueProcessingPredicate.bind(this), this.shouldMakePrettyDiff); + const diffResult = computeDiff(this.original, this.modified, this.continueLineDiff, this.shouldMakePrettyDiff); + const rawChanges = diffResult.changes; + const quitEarly = diffResult.quitEarly; // The diff is always computed with ignoring trim whitespace // This ensures we get the prettiest diff if (this.shouldIgnoreTrimWhitespace) { - let lineChanges: LineChange[] = []; + const lineChanges: LineChange[] = []; for (let i = 0, length = rawChanges.length; i < length; i++) { - lineChanges.push(LineChange.createFromDiffResult(this.shouldIgnoreTrimWhitespace, rawChanges[i], this.original, this.modified, this._continueProcessingPredicate.bind(this), this.shouldComputeCharChanges, this.shouldPostProcessCharChanges)); + lineChanges.push(LineChange.createFromDiffResult(this.shouldIgnoreTrimWhitespace, rawChanges[i], this.original, this.modified, this.continueCharDiff, this.shouldComputeCharChanges, this.shouldPostProcessCharChanges)); } - return lineChanges; + return { + quitEarly: quitEarly, + changes: lineChanges + }; } // Need to post-process and introduce changes where the trim whitespace is different // Note that we are looping starting at -1 to also cover the lines before the first change - let result: LineChange[] = []; + const result: LineChange[] = []; let originalLineIndex = 0; let modifiedLineIndex = 0; @@ -409,8 +395,8 @@ export class DiffComputer { // Check the leading whitespace { - let originalStartColumn = LineMarkerSequence._getFirstNonBlankColumn(originalLine, 1); - let modifiedStartColumn = LineMarkerSequence._getFirstNonBlankColumn(modifiedLine, 1); + let originalStartColumn = getFirstNonBlankColumn(originalLine, 1); + let modifiedStartColumn = getFirstNonBlankColumn(modifiedLine, 1); while (originalStartColumn > 1 && modifiedStartColumn > 1) { const originalChar = originalLine.charCodeAt(originalStartColumn - 2); const modifiedChar = modifiedLine.charCodeAt(modifiedStartColumn - 2); @@ -431,8 +417,8 @@ export class DiffComputer { // Check the trailing whitespace { - let originalEndColumn = LineMarkerSequence._getLastNonBlankColumn(originalLine, 1); - let modifiedEndColumn = LineMarkerSequence._getLastNonBlankColumn(modifiedLine, 1); + let originalEndColumn = getLastNonBlankColumn(originalLine, 1); + let modifiedEndColumn = getLastNonBlankColumn(modifiedLine, 1); const originalMaxColumn = originalLine.length + 1; const modifiedMaxColumn = modifiedLine.length + 1; while (originalEndColumn < originalMaxColumn && modifiedEndColumn < modifiedMaxColumn) { @@ -459,14 +445,17 @@ export class DiffComputer { if (nextChange) { // Emit the actual change - result.push(LineChange.createFromDiffResult(this.shouldIgnoreTrimWhitespace, nextChange, this.original, this.modified, this._continueProcessingPredicate.bind(this), this.shouldComputeCharChanges, this.shouldPostProcessCharChanges)); + result.push(LineChange.createFromDiffResult(this.shouldIgnoreTrimWhitespace, nextChange, this.original, this.modified, this.continueCharDiff, this.shouldComputeCharChanges, this.shouldPostProcessCharChanges)); originalLineIndex += nextChange.originalLength; modifiedLineIndex += nextChange.modifiedLength; } } - return result; + return { + quitEarly: quitEarly, + changes: result + }; } private _pushTrimWhitespaceCharChange( @@ -513,8 +502,8 @@ export class DiffComputer { if (prevChange.originalEndLineNumber + 1 === originalLineNumber && prevChange.modifiedEndLineNumber + 1 === modifiedLineNumber) { prevChange.originalEndLineNumber = originalLineNumber; prevChange.modifiedEndLineNumber = modifiedLineNumber; - if (this.shouldComputeCharChanges) { - prevChange.charChanges!.push(new CharChange( + if (this.shouldComputeCharChanges && prevChange.charChanges) { + prevChange.charChanges.push(new CharChange( originalLineNumber, originalStartColumn, originalLineNumber, originalEndColumn, modifiedLineNumber, modifiedStartColumn, modifiedLineNumber, modifiedEndColumn )); @@ -524,13 +513,31 @@ export class DiffComputer { return false; } +} - private _continueProcessingPredicate(): boolean { - if (this.maximumRunTimeMs === 0) { - return true; - } - const now = (new Date()).getTime(); - return now - this.computationStartTime < this.maximumRunTimeMs; +function getFirstNonBlankColumn(txt: string, defaultValue: number): number { + const r = strings.firstNonWhitespaceIndex(txt); + if (r === -1) { + return defaultValue; + } + return r + 1; +} + +function getLastNonBlankColumn(txt: string, defaultValue: number): number { + const r = strings.lastNonWhitespaceIndex(txt); + if (r === -1) { + return defaultValue; + } + return r + 2; +} + +function createContinueProcessingPredicate(maximumRuntime: number): () => boolean { + if (maximumRuntime === 0) { + return () => true; } + const startTime = Date.now(); + return () => { + return Date.now() - startTime < maximumRuntime; + }; } diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index 1cec833095..3c70f8432b 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -315,6 +315,12 @@ export interface IEditor { */ getVisibleColumnFromPosition(position: IPosition): number; + /** + * Given a position, returns a column number that takes tab-widths into account. + * @internal + */ + getStatusbarColumn(position: IPosition): number; + /** * Returns the primary position of the cursor. */ @@ -488,10 +494,6 @@ export interface IDiffEditor extends IEditor { * An editor contribution that gets created every time a new editor gets created and gets disposed when the editor gets disposed. */ export interface IEditorContribution { - /** - * Get a unique identifier for this contribution. - */ - getId(): string; /** * Dispose this contribution. */ @@ -506,6 +508,17 @@ export interface IEditorContribution { restoreViewState?(state: any): void; } +/** + * A diff editor contribution that gets created every time a new diffeditor gets created and gets disposed when the diff editor gets disposed. + * @internal + */ +export interface IDiffEditorContribution { + /** + * Dispose this contribution. + */ + dispose(): void; +} + /** * @internal */ diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 06a60f913a..6f6bc288be 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -459,8 +459,8 @@ export class FindMatch { */ export interface IFoundBracket { range: Range; - open: string; - close: string; + open: string[]; + close: string[]; isOpen: boolean; } @@ -608,6 +608,12 @@ export interface ITextModel { */ getValueLengthInRange(range: IRange): number; + /** + * Get the character count of text in a certain range. + * @param range The range describing what text length to get. + */ + getCharacterCountInRange(range: IRange): number; + /** * Splits characters in two buckets. First bucket (A) is of characters that * sit in lines with length < `LONG_LINE_BOUNDARY`. Second bucket (B) is of @@ -881,6 +887,13 @@ export interface ITextModel { */ findNextBracket(position: IPosition): IFoundBracket | null; + /** + * Find the enclosing brackets that contain `position`. + * @param position The position at which to start the search. + * @internal + */ + findEnclosingBrackets(position: IPosition): [Range, Range] | null; + /** * Given a `position`, if the position is on top or near a bracket, * find the matching bracket of that bracket and return the ranges of both brackets. @@ -1196,6 +1209,7 @@ export interface ITextBuffer { getValueInRange(range: Range, eol: EndOfLinePreference): string; createSnapshot(preserveBOM: boolean): ITextSnapshot; getValueLengthInRange(range: Range, eol: EndOfLinePreference): number; + getCharacterCountInRange(range: Range, eol: EndOfLinePreference): number; getLength(): number; getLineCount(): number; getLinesContent(): string[]; diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts index 2060fcb476..9e4c3af46b 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts @@ -106,6 +106,37 @@ export class PieceTreeTextBuffer implements ITextBuffer { return endOffset - startOffset; } + public getCharacterCountInRange(range: Range, eol: EndOfLinePreference = EndOfLinePreference.TextDefined): number { + if (this._mightContainNonBasicASCII) { + // we must count by iterating + + let result = 0; + + const fromLineNumber = range.startLineNumber; + const toLineNumber = range.endLineNumber; + for (let lineNumber = fromLineNumber; lineNumber <= toLineNumber; lineNumber++) { + const lineContent = this.getLineContent(lineNumber); + const fromOffset = (lineNumber === fromLineNumber ? range.startColumn - 1 : 0); + const toOffset = (lineNumber === toLineNumber ? range.endColumn - 1 : lineContent.length); + + for (let offset = fromOffset; offset < toOffset; offset++) { + if (strings.isHighSurrogate(lineContent.charCodeAt(offset))) { + result = result + 1; + offset = offset + 1; + } else { + result = result + 1; + } + } + } + + result += this._getEndOfLine(eol).length * (toLineNumber - fromLineNumber); + + return result; + } + + return this.getValueLengthInRange(range, eol); + } + public getLength(): number { return this._pieceTree.getLength(); } diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 8a94c5a8f7..00f196a815 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -726,6 +726,11 @@ export class TextModel extends Disposable implements model.ITextModel { return this._buffer.getValueLengthInRange(this.validateRange(rawRange), eol); } + public getCharacterCountInRange(rawRange: IRange, eol: model.EndOfLinePreference = model.EndOfLinePreference.TextDefined): number { + this._assertNotDisposed(); + return this._buffer.getCharacterCountInRange(this.validateRange(rawRange), eol); + } + public getLineCount(): number { this._assertNotDisposed(); return this._buffer.getLineCount(); @@ -1910,7 +1915,7 @@ export class TextModel extends Disposable implements model.ITextModel { const lineTokens = this._getLineTokens(lineNumber); const lineText = this._buffer.getLineContent(lineNumber); - let tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1); + const tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1); if (tokenIndex < 0) { return null; } @@ -1919,15 +1924,15 @@ export class TextModel extends Disposable implements model.ITextModel { // check that the token is not to be ignored if (currentModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))) { // limit search to not go before `maxBracketLength` - let searchStartOffset = Math.max(lineTokens.getStartOffset(tokenIndex), position.column - 1 - currentModeBrackets.maxBracketLength); + let searchStartOffset = Math.max(0, position.column - 1 - currentModeBrackets.maxBracketLength); // limit search to not go after `maxBracketLength` - const searchEndOffset = Math.min(lineTokens.getEndOffset(tokenIndex), position.column - 1 + currentModeBrackets.maxBracketLength); + const searchEndOffset = Math.min(lineText.length, position.column - 1 + currentModeBrackets.maxBracketLength); // it might be the case that [currentTokenStart -> currentTokenEnd] contains multiple brackets // `bestResult` will contain the most right-side result let bestResult: [Range, Range] | null = null; while (true) { - let foundBracket = BracketsUtils.findNextBracketInToken(currentModeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); + const foundBracket = BracketsUtils.findNextBracketInRange(currentModeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); if (!foundBracket) { // there are no more brackets in this text break; @@ -1935,12 +1940,8 @@ export class TextModel extends Disposable implements model.ITextModel { // check that we didn't hit a bracket too far away from position if (foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) { - let foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1); - foundBracketText = foundBracketText.toLowerCase(); - - let r = this._matchFoundBracket(foundBracket, currentModeBrackets.textIsBracket[foundBracketText], currentModeBrackets.textIsOpenBracket[foundBracketText]); - - // check that we can actually match this bracket + const foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1).toLowerCase(); + const r = this._matchFoundBracket(foundBracket, currentModeBrackets.textIsBracket[foundBracketText], currentModeBrackets.textIsOpenBracket[foundBracketText]); if (r) { bestResult = r; } @@ -1956,24 +1957,20 @@ export class TextModel extends Disposable implements model.ITextModel { // If position is in between two tokens, try also looking in the previous token if (tokenIndex > 0 && lineTokens.getStartOffset(tokenIndex) === position.column - 1) { - const searchEndOffset = lineTokens.getStartOffset(tokenIndex); - tokenIndex--; - const prevModeBrackets = LanguageConfigurationRegistry.getBracketsSupport(lineTokens.getLanguageId(tokenIndex)); + const prevTokenIndex = tokenIndex - 1; + const prevModeBrackets = LanguageConfigurationRegistry.getBracketsSupport(lineTokens.getLanguageId(prevTokenIndex)); // check that previous token is not to be ignored - if (prevModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))) { + if (prevModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(prevTokenIndex))) { // limit search in case previous token is very large, there's no need to go beyond `maxBracketLength` - const searchStartOffset = Math.max(lineTokens.getStartOffset(tokenIndex), position.column - 1 - prevModeBrackets.maxBracketLength); - const foundBracket = BracketsUtils.findPrevBracketInToken(prevModeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); + const searchStartOffset = Math.max(0, position.column - 1 - prevModeBrackets.maxBracketLength); + const searchEndOffset = Math.min(lineText.length, position.column - 1 + prevModeBrackets.maxBracketLength); + const foundBracket = BracketsUtils.findPrevBracketInRange(prevModeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); // check that we didn't hit a bracket too far away from position if (foundBracket && foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) { - let foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1); - foundBracketText = foundBracketText.toLowerCase(); - - let r = this._matchFoundBracket(foundBracket, prevModeBrackets.textIsBracket[foundBracketText], prevModeBrackets.textIsOpenBracket[foundBracketText]); - - // check that we can actually match this bracket + const foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1).toLowerCase(); + const r = this._matchFoundBracket(foundBracket, prevModeBrackets.textIsBracket[foundBracketText], prevModeBrackets.textIsOpenBracket[foundBracketText]); if (r) { return r; } @@ -2011,54 +2008,76 @@ export class TextModel extends Disposable implements model.ITextModel { const reversedBracketRegex = bracket.reversedRegex; let count = -1; + const searchPrevMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null => { + while (true) { + const r = BracketsUtils.findPrevBracketInRange(reversedBracketRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); + if (!r) { + break; + } + + const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase(); + if (bracket.isOpen(hitText)) { + count++; + } else if (bracket.isClose(hitText)) { + count--; + } + + if (count === 0) { + return r; + } + + searchEndOffset = r.startColumn - 1; + } + + return null; + }; + for (let lineNumber = position.lineNumber; lineNumber >= 1; lineNumber--) { const lineTokens = this._getLineTokens(lineNumber); const tokenCount = lineTokens.getCount(); const lineText = this._buffer.getLineContent(lineNumber); let tokenIndex = tokenCount - 1; - let searchStopOffset = -1; + let searchStartOffset = lineText.length; + let searchEndOffset = lineText.length; if (lineNumber === position.lineNumber) { tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1); - searchStopOffset = position.column - 1; + searchStartOffset = position.column - 1; + searchEndOffset = position.column - 1; } + let prevSearchInToken = true; for (; tokenIndex >= 0; tokenIndex--) { - const tokenLanguageId = lineTokens.getLanguageId(tokenIndex); - const tokenType = lineTokens.getStandardTokenType(tokenIndex); - const tokenStartOffset = lineTokens.getStartOffset(tokenIndex); - const tokenEndOffset = lineTokens.getEndOffset(tokenIndex); + const searchInToken = (lineTokens.getLanguageId(tokenIndex) === languageId && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))); - if (searchStopOffset === -1) { - searchStopOffset = tokenEndOffset; - } - - if (tokenLanguageId === languageId && !ignoreBracketsInToken(tokenType)) { - - while (true) { - let r = BracketsUtils.findPrevBracketInToken(reversedBracketRegex, lineNumber, lineText, tokenStartOffset, searchStopOffset); - if (!r) { - break; - } - - let hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1); - hitText = hitText.toLowerCase(); - - if (hitText === bracket.open) { - count++; - } else if (hitText === bracket.close) { - count--; - } - - if (count === 0) { + if (searchInToken) { + // this token should be searched + if (prevSearchInToken) { + // the previous token should be searched, simply extend searchStartOffset + searchStartOffset = lineTokens.getStartOffset(tokenIndex); + } else { + // the previous token should not be searched + searchStartOffset = lineTokens.getStartOffset(tokenIndex); + searchEndOffset = lineTokens.getEndOffset(tokenIndex); + } + } else { + // this token should not be searched + if (prevSearchInToken && searchStartOffset !== searchEndOffset) { + const r = searchPrevMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset); + if (r) { return r; } - - searchStopOffset = r.startColumn - 1; } } - searchStopOffset = -1; + prevSearchInToken = searchInToken; + } + + if (prevSearchInToken && searchStartOffset !== searchEndOffset) { + const r = searchPrevMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset); + if (r) { + return r; + } } } @@ -2072,53 +2091,77 @@ export class TextModel extends Disposable implements model.ITextModel { const bracketRegex = bracket.forwardRegex; let count = 1; - for (let lineNumber = position.lineNumber, lineCount = this.getLineCount(); lineNumber <= lineCount; lineNumber++) { + const searchNextMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null => { + while (true) { + const r = BracketsUtils.findNextBracketInRange(bracketRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); + if (!r) { + break; + } + + const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase(); + if (bracket.isOpen(hitText)) { + count++; + } else if (bracket.isClose(hitText)) { + count--; + } + + if (count === 0) { + return r; + } + + searchStartOffset = r.endColumn - 1; + } + + return null; + }; + + const lineCount = this.getLineCount(); + for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) { const lineTokens = this._getLineTokens(lineNumber); const tokenCount = lineTokens.getCount(); const lineText = this._buffer.getLineContent(lineNumber); let tokenIndex = 0; let searchStartOffset = 0; + let searchEndOffset = 0; if (lineNumber === position.lineNumber) { tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1); searchStartOffset = position.column - 1; + searchEndOffset = position.column - 1; } + let prevSearchInToken = true; for (; tokenIndex < tokenCount; tokenIndex++) { - const tokenLanguageId = lineTokens.getLanguageId(tokenIndex); - const tokenType = lineTokens.getStandardTokenType(tokenIndex); - const tokenStartOffset = lineTokens.getStartOffset(tokenIndex); - const tokenEndOffset = lineTokens.getEndOffset(tokenIndex); + const searchInToken = (lineTokens.getLanguageId(tokenIndex) === languageId && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))); - if (searchStartOffset === 0) { - searchStartOffset = tokenStartOffset; - } - - if (tokenLanguageId === languageId && !ignoreBracketsInToken(tokenType)) { - while (true) { - let r = BracketsUtils.findNextBracketInToken(bracketRegex, lineNumber, lineText, searchStartOffset, tokenEndOffset); - if (!r) { - break; - } - - let hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1); - hitText = hitText.toLowerCase(); - - if (hitText === bracket.open) { - count++; - } else if (hitText === bracket.close) { - count--; - } - - if (count === 0) { + if (searchInToken) { + // this token should be searched + if (prevSearchInToken) { + // the previous token should be searched, simply extend searchEndOffset + searchEndOffset = lineTokens.getEndOffset(tokenIndex); + } else { + // the previous token should not be searched + searchStartOffset = lineTokens.getStartOffset(tokenIndex); + searchEndOffset = lineTokens.getEndOffset(tokenIndex); + } + } else { + // this token should not be searched + if (prevSearchInToken && searchStartOffset !== searchEndOffset) { + const r = searchNextMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset); + if (r) { return r; } - - searchStartOffset = r.endColumn - 1; } } - searchStartOffset = 0; + prevSearchInToken = searchInToken; + } + + if (prevSearchInToken && searchStartOffset !== searchEndOffset) { + const r = searchNextMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset); + if (r) { + return r; + } } } @@ -2136,33 +2179,66 @@ export class TextModel extends Disposable implements model.ITextModel { const lineText = this._buffer.getLineContent(lineNumber); let tokenIndex = tokenCount - 1; - let searchStopOffset = -1; + let searchStartOffset = lineText.length; + let searchEndOffset = lineText.length; if (lineNumber === position.lineNumber) { tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1); - searchStopOffset = position.column - 1; - } - - for (; tokenIndex >= 0; tokenIndex--) { + searchStartOffset = position.column - 1; + searchEndOffset = position.column - 1; const tokenLanguageId = lineTokens.getLanguageId(tokenIndex); - const tokenType = lineTokens.getStandardTokenType(tokenIndex); - const tokenStartOffset = lineTokens.getStartOffset(tokenIndex); - const tokenEndOffset = lineTokens.getEndOffset(tokenIndex); - - if (searchStopOffset === -1) { - searchStopOffset = tokenEndOffset; - } if (languageId !== tokenLanguageId) { languageId = tokenLanguageId; modeBrackets = LanguageConfigurationRegistry.getBracketsSupport(languageId); } - if (modeBrackets && !ignoreBracketsInToken(tokenType)) { - let r = BracketsUtils.findPrevBracketInToken(modeBrackets.reversedRegex, lineNumber, lineText, tokenStartOffset, searchStopOffset); - if (r) { - return this._toFoundBracket(modeBrackets, r); + } + + let prevSearchInToken = true; + for (; tokenIndex >= 0; tokenIndex--) { + const tokenLanguageId = lineTokens.getLanguageId(tokenIndex); + + if (languageId !== tokenLanguageId) { + // language id change! + if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) { + const r = BracketsUtils.findPrevBracketInRange(modeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); + if (r) { + return this._toFoundBracket(modeBrackets, r); + } + prevSearchInToken = false; + } + languageId = tokenLanguageId; + modeBrackets = LanguageConfigurationRegistry.getBracketsSupport(languageId); + } + + const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))); + + if (searchInToken) { + // this token should be searched + if (prevSearchInToken) { + // the previous token should be searched, simply extend searchStartOffset + searchStartOffset = lineTokens.getStartOffset(tokenIndex); + } else { + // the previous token should not be searched + searchStartOffset = lineTokens.getStartOffset(tokenIndex); + searchEndOffset = lineTokens.getEndOffset(tokenIndex); + } + } else { + // this token should not be searched + if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) { + const r = BracketsUtils.findPrevBracketInRange(modeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); + if (r) { + return this._toFoundBracket(modeBrackets, r); + } } } - searchStopOffset = -1; + prevSearchInToken = searchInToken; + } + + if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) { + const r = BracketsUtils.findPrevBracketInRange(modeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); + if (r) { + return this._toFoundBracket(modeBrackets, r); + } } } @@ -2171,43 +2247,187 @@ export class TextModel extends Disposable implements model.ITextModel { public findNextBracket(_position: IPosition): model.IFoundBracket | null { const position = this.validatePosition(_position); + const lineCount = this.getLineCount(); let languageId: LanguageId = -1; let modeBrackets: RichEditBrackets | null = null; - for (let lineNumber = position.lineNumber, lineCount = this.getLineCount(); lineNumber <= lineCount; lineNumber++) { + for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) { const lineTokens = this._getLineTokens(lineNumber); const tokenCount = lineTokens.getCount(); const lineText = this._buffer.getLineContent(lineNumber); let tokenIndex = 0; let searchStartOffset = 0; + let searchEndOffset = 0; if (lineNumber === position.lineNumber) { tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1); searchStartOffset = position.column - 1; - } - - for (; tokenIndex < tokenCount; tokenIndex++) { + searchEndOffset = position.column - 1; const tokenLanguageId = lineTokens.getLanguageId(tokenIndex); - const tokenType = lineTokens.getStandardTokenType(tokenIndex); - const tokenStartOffset = lineTokens.getStartOffset(tokenIndex); - const tokenEndOffset = lineTokens.getEndOffset(tokenIndex); - - if (searchStartOffset === 0) { - searchStartOffset = tokenStartOffset; - } - if (languageId !== tokenLanguageId) { languageId = tokenLanguageId; modeBrackets = LanguageConfigurationRegistry.getBracketsSupport(languageId); } - if (modeBrackets && !ignoreBracketsInToken(tokenType)) { - let r = BracketsUtils.findNextBracketInToken(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, tokenEndOffset); - if (r) { - return this._toFoundBracket(modeBrackets, r); + } + + let prevSearchInToken = true; + for (; tokenIndex < tokenCount; tokenIndex++) { + const tokenLanguageId = lineTokens.getLanguageId(tokenIndex); + + if (languageId !== tokenLanguageId) { + // language id change! + if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) { + const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); + if (r) { + return this._toFoundBracket(modeBrackets, r); + } + prevSearchInToken = false; + } + languageId = tokenLanguageId; + modeBrackets = LanguageConfigurationRegistry.getBracketsSupport(languageId); + } + + const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))); + if (searchInToken) { + // this token should be searched + if (prevSearchInToken) { + // the previous token should be searched, simply extend searchEndOffset + searchEndOffset = lineTokens.getEndOffset(tokenIndex); + } else { + // the previous token should not be searched + searchStartOffset = lineTokens.getStartOffset(tokenIndex); + searchEndOffset = lineTokens.getEndOffset(tokenIndex); + } + } else { + // this token should not be searched + if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) { + const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); + if (r) { + return this._toFoundBracket(modeBrackets, r); + } } } - searchStartOffset = 0; + prevSearchInToken = searchInToken; + } + + if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) { + const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); + if (r) { + return this._toFoundBracket(modeBrackets, r); + } + } + } + + return null; + } + + public findEnclosingBrackets(_position: IPosition): [Range, Range] | null { + const position = this.validatePosition(_position); + const lineCount = this.getLineCount(); + + let counts: number[] = []; + const resetCounts = (modeBrackets: RichEditBrackets | null) => { + counts = []; + for (let i = 0, len = modeBrackets ? modeBrackets.brackets.length : 0; i < len; i++) { + counts[i] = 0; + } + }; + const searchInRange = (modeBrackets: RichEditBrackets, lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): [Range, Range] | null => { + while (true) { + const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); + if (!r) { + break; + } + + const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase(); + const bracket = modeBrackets.textIsBracket[hitText]; + if (bracket) { + if (bracket.isOpen(hitText)) { + counts[bracket.index]++; + } else if (bracket.isClose(hitText)) { + counts[bracket.index]--; + } + + if (counts[bracket.index] === -1) { + return this._matchFoundBracket(r, bracket, false); + } + } + + searchStartOffset = r.endColumn - 1; + } + return null; + }; + + let languageId: LanguageId = -1; + let modeBrackets: RichEditBrackets | null = null; + for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) { + const lineTokens = this._getLineTokens(lineNumber); + const tokenCount = lineTokens.getCount(); + const lineText = this._buffer.getLineContent(lineNumber); + + let tokenIndex = 0; + let searchStartOffset = 0; + let searchEndOffset = 0; + if (lineNumber === position.lineNumber) { + tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1); + searchStartOffset = position.column - 1; + searchEndOffset = position.column - 1; + const tokenLanguageId = lineTokens.getLanguageId(tokenIndex); + if (languageId !== tokenLanguageId) { + languageId = tokenLanguageId; + modeBrackets = LanguageConfigurationRegistry.getBracketsSupport(languageId); + resetCounts(modeBrackets); + } + } + + let prevSearchInToken = true; + for (; tokenIndex < tokenCount; tokenIndex++) { + const tokenLanguageId = lineTokens.getLanguageId(tokenIndex); + + if (languageId !== tokenLanguageId) { + // language id change! + if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) { + const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset); + if (r) { + return r; + } + prevSearchInToken = false; + } + languageId = tokenLanguageId; + modeBrackets = LanguageConfigurationRegistry.getBracketsSupport(languageId); + resetCounts(modeBrackets); + } + + const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))); + if (searchInToken) { + // this token should be searched + if (prevSearchInToken) { + // the previous token should be searched, simply extend searchEndOffset + searchEndOffset = lineTokens.getEndOffset(tokenIndex); + } else { + // the previous token should not be searched + searchStartOffset = lineTokens.getStartOffset(tokenIndex); + searchEndOffset = lineTokens.getEndOffset(tokenIndex); + } + } else { + // this token should not be searched + if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) { + const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset); + if (r) { + return r; + } + } + } + + prevSearchInToken = searchInToken; + } + + if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) { + const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset); + if (r) { + return r; + } } } diff --git a/src/vs/editor/common/model/textModelSearch.ts b/src/vs/editor/common/model/textModelSearch.ts index c7506a99ed..b8d0685b31 100644 --- a/src/vs/editor/common/model/textModelSearch.ts +++ b/src/vs/editor/common/model/textModelSearch.ts @@ -85,7 +85,7 @@ export function isMultilineRegexSource(searchString: string): boolean { } const nextChCode = searchString.charCodeAt(i); - if (nextChCode === CharCode.n || nextChCode === CharCode.r || nextChCode === CharCode.W) { + if (nextChCode === CharCode.n || nextChCode === CharCode.r || nextChCode === CharCode.W || nextChCode === CharCode.w) { return true; } } diff --git a/src/vs/editor/common/model/textModelTokens.ts b/src/vs/editor/common/model/textModelTokens.ts index f9eb39cc13..a51075294a 100644 --- a/src/vs/editor/common/model/textModelTokens.ts +++ b/src/vs/editor/common/model/textModelTokens.ts @@ -16,6 +16,7 @@ import { TextModel } from 'vs/editor/common/model/textModel'; import { Disposable } from 'vs/base/common/lifecycle'; import { StopWatch } from 'vs/base/common/stopwatch'; import { MultilineTokensBuilder, countEOL } from 'vs/editor/common/model/tokensStore'; +import * as platform from 'vs/base/common/platform'; const enum Constants { CHEAP_TOKENIZATION_LENGTH_LIMIT = 2048 @@ -196,14 +197,14 @@ export class TextModelTokenization extends Disposable { private readonly _textModel: TextModel; private readonly _tokenizationStateStore: TokenizationStateStore; - private _revalidateTokensTimeout: any; + private _isDisposed: boolean; private _tokenizationSupport: ITokenizationSupport | null; constructor(textModel: TextModel) { super(); + this._isDisposed = false; this._textModel = textModel; this._tokenizationStateStore = new TokenizationStateStore(); - this._revalidateTokensTimeout = -1; this._tokenizationSupport = null; this._register(TokenizationRegistry.onDidChange((e) => { @@ -246,19 +247,11 @@ export class TextModelTokenization extends Disposable { } public dispose(): void { - this._clearTimers(); + this._isDisposed = true; super.dispose(); } - private _clearTimers(): void { - if (this._revalidateTokensTimeout !== -1) { - clearTimeout(this._revalidateTokensTimeout); - this._revalidateTokensTimeout = -1; - } - } - private _resetTokenizationState(): void { - this._clearTimers(); const [tokenizationSupport, initialState] = initializeTokenization(this._textModel); this._tokenizationSupport = tokenizationSupport; this._tokenizationStateStore.flush(initialState); @@ -266,16 +259,19 @@ export class TextModelTokenization extends Disposable { } private _beginBackgroundTokenization(): void { - if (this._textModel.isAttachedToEditor() && this._hasLinesToTokenize() && this._revalidateTokensTimeout === -1) { - this._revalidateTokensTimeout = setTimeout(() => { - this._revalidateTokensTimeout = -1; + if (this._textModel.isAttachedToEditor() && this._hasLinesToTokenize()) { + platform.setImmediate(() => { + if (this._isDisposed) { + // disposed in the meantime + return; + } this._revalidateTokensNow(); - }, 0); + }); } } private _revalidateTokensNow(toLineNumber: number = this._textModel.getLineCount()): void { - const MAX_ALLOWED_TIME = 20; + const MAX_ALLOWED_TIME = 1; const builder = new MultilineTokensBuilder(); const sw = StopWatch.create(false); diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index cbab3921d5..8072e0ed1a 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -19,6 +19,7 @@ import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureR import { TokenizationRegistryImpl } from 'vs/editor/common/modes/tokenizationRegistry'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IMarkerData } from 'vs/platform/markers/common/markers'; +import { keys } from 'vs/base/common/map'; /** * Open ended enum at runtime @@ -874,40 +875,88 @@ export const enum SymbolTag { /** * @internal */ -export const symbolKindToCssClass = (function () { +export namespace SymbolKinds { - const _fromMapping: { [n: number]: string } = Object.create(null); - _fromMapping[SymbolKind.File] = 'file'; - _fromMapping[SymbolKind.Module] = 'module'; - _fromMapping[SymbolKind.Namespace] = 'namespace'; - _fromMapping[SymbolKind.Package] = 'package'; - _fromMapping[SymbolKind.Class] = 'class'; - _fromMapping[SymbolKind.Method] = 'method'; - _fromMapping[SymbolKind.Property] = 'property'; - _fromMapping[SymbolKind.Field] = 'field'; - _fromMapping[SymbolKind.Constructor] = 'constructor'; - _fromMapping[SymbolKind.Enum] = 'enum'; - _fromMapping[SymbolKind.Interface] = 'interface'; - _fromMapping[SymbolKind.Function] = 'function'; - _fromMapping[SymbolKind.Variable] = 'variable'; - _fromMapping[SymbolKind.Constant] = 'constant'; - _fromMapping[SymbolKind.String] = 'string'; - _fromMapping[SymbolKind.Number] = 'number'; - _fromMapping[SymbolKind.Boolean] = 'boolean'; - _fromMapping[SymbolKind.Array] = 'array'; - _fromMapping[SymbolKind.Object] = 'object'; - _fromMapping[SymbolKind.Key] = 'key'; - _fromMapping[SymbolKind.Null] = 'null'; - _fromMapping[SymbolKind.EnumMember] = 'enum-member'; - _fromMapping[SymbolKind.Struct] = 'struct'; - _fromMapping[SymbolKind.Event] = 'event'; - _fromMapping[SymbolKind.Operator] = 'operator'; - _fromMapping[SymbolKind.TypeParameter] = 'type-parameter'; + const byName = new Map(); + byName.set('file', SymbolKind.File); + byName.set('module', SymbolKind.Module); + byName.set('namespace', SymbolKind.Namespace); + byName.set('package', SymbolKind.Package); + byName.set('class', SymbolKind.Class); + byName.set('method', SymbolKind.Method); + byName.set('property', SymbolKind.Property); + byName.set('field', SymbolKind.Field); + byName.set('constructor', SymbolKind.Constructor); + byName.set('enum', SymbolKind.Enum); + byName.set('interface', SymbolKind.Interface); + byName.set('function', SymbolKind.Function); + byName.set('variable', SymbolKind.Variable); + byName.set('constant', SymbolKind.Constant); + byName.set('string', SymbolKind.String); + byName.set('number', SymbolKind.Number); + byName.set('boolean', SymbolKind.Boolean); + byName.set('array', SymbolKind.Array); + byName.set('object', SymbolKind.Object); + byName.set('key', SymbolKind.Key); + byName.set('null', SymbolKind.Null); + byName.set('enum-member', SymbolKind.EnumMember); + byName.set('struct', SymbolKind.Struct); + byName.set('event', SymbolKind.Event); + byName.set('operator', SymbolKind.Operator); + byName.set('type-parameter', SymbolKind.TypeParameter); - return function toCssClassName(kind: SymbolKind, inline?: boolean): string { - return `symbol-icon ${inline ? 'inline' : 'block'} ${_fromMapping[kind] || 'property'}`; - }; -})(); + const byKind = new Map(); + byKind.set(SymbolKind.File, 'file'); + byKind.set(SymbolKind.Module, 'module'); + byKind.set(SymbolKind.Namespace, 'namespace'); + byKind.set(SymbolKind.Package, 'package'); + byKind.set(SymbolKind.Class, 'class'); + byKind.set(SymbolKind.Method, 'method'); + byKind.set(SymbolKind.Property, 'property'); + byKind.set(SymbolKind.Field, 'field'); + byKind.set(SymbolKind.Constructor, 'constructor'); + byKind.set(SymbolKind.Enum, 'enum'); + byKind.set(SymbolKind.Interface, 'interface'); + byKind.set(SymbolKind.Function, 'function'); + byKind.set(SymbolKind.Variable, 'variable'); + byKind.set(SymbolKind.Constant, 'constant'); + byKind.set(SymbolKind.String, 'string'); + byKind.set(SymbolKind.Number, 'number'); + byKind.set(SymbolKind.Boolean, 'boolean'); + byKind.set(SymbolKind.Array, 'array'); + byKind.set(SymbolKind.Object, 'object'); + byKind.set(SymbolKind.Key, 'key'); + byKind.set(SymbolKind.Null, 'null'); + byKind.set(SymbolKind.EnumMember, 'enum-member'); + byKind.set(SymbolKind.Struct, 'struct'); + byKind.set(SymbolKind.Event, 'event'); + byKind.set(SymbolKind.Operator, 'operator'); + byKind.set(SymbolKind.TypeParameter, 'type-parameter'); + /** + * @internal + */ + export function fromString(value: string): SymbolKind | undefined { + return byName.get(value); + } + /** + * @internal + */ + export function names(): readonly string[] { + return keys(byName); + } + /** + * @internal + */ + export function toString(kind: SymbolKind): string | undefined { + return byKind.get(kind); + } + /** + * @internal + */ + export function toCssClassName(kind: SymbolKind, inline?: boolean): string { + return `symbol-icon ${inline ? 'inline' : 'block'} ${byKind.get(kind) || 'property'}`; + } +} export interface DocumentSymbol { name: string; diff --git a/src/vs/editor/common/modes/languageConfiguration.ts b/src/vs/editor/common/modes/languageConfiguration.ts index d64d5ad623..ce8479a254 100644 --- a/src/vs/editor/common/modes/languageConfiguration.ts +++ b/src/vs/editor/common/modes/languageConfiguration.ts @@ -247,7 +247,7 @@ export class StandardAutoClosingPairConditional { if (Array.isArray(source.notIn)) { for (let i = 0, len = source.notIn.length; i < len; i++) { - let notIn = source.notIn[i]; + const notIn: string = source.notIn[i]; switch (notIn) { case 'string': this._standardTokenMask |= StandardTokenType.String; diff --git a/src/vs/editor/common/modes/linkComputer.ts b/src/vs/editor/common/modes/linkComputer.ts index 0ac58a68f3..c3a62b262b 100644 --- a/src/vs/editor/common/modes/linkComputer.ts +++ b/src/vs/editor/common/modes/linkComputer.ts @@ -5,7 +5,6 @@ import { CharCode } from 'vs/base/common/charCode'; import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier'; -import { Uint8Matrix } from 'vs/editor/common/core/uint'; import { ILink } from 'vs/editor/common/modes'; export interface ILinkComputerTarget { @@ -33,6 +32,32 @@ export const enum State { export type Edge = [State, number, State]; +export class Uint8Matrix { + + private readonly _data: Uint8Array; + public readonly rows: number; + public readonly cols: number; + + constructor(rows: number, cols: number, defaultValue: number) { + const data = new Uint8Array(rows * cols); + for (let i = 0, len = rows * cols; i < len; i++) { + data[i] = defaultValue; + } + + this._data = data; + this.rows = rows; + this.cols = cols; + } + + public get(row: number, col: number): number { + return this._data[row * this.cols + col]; + } + + public set(row: number, col: number, value: number): void { + this._data[row * this.cols + col] = value; + } +} + export class StateMachine { private readonly _states: Uint8Matrix; diff --git a/src/vs/editor/common/modes/modesRegistry.ts b/src/vs/editor/common/modes/modesRegistry.ts index 516b59532c..f7a5526526 100644 --- a/src/vs/editor/common/modes/modesRegistry.ts +++ b/src/vs/editor/common/modes/modesRegistry.ts @@ -60,5 +60,17 @@ LanguageConfigurationRegistry.register(PLAINTEXT_LANGUAGE_IDENTIFIER, { ['(', ')'], ['[', ']'], ['{', '}'], - ] + ], + surroundingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '<', close: '>' }, + { open: '\"', close: '\"' }, + { open: '\'', close: '\'' }, + { open: '`', close: '`' }, + ], + folding: { + offSide: true + } }); diff --git a/src/vs/editor/common/modes/supports/electricCharacter.ts b/src/vs/editor/common/modes/supports/electricCharacter.ts index ddd2527038..b1de661514 100644 --- a/src/vs/editor/common/modes/supports/electricCharacter.ts +++ b/src/vs/editor/common/modes/supports/electricCharacter.ts @@ -28,10 +28,11 @@ export class BracketElectricCharacterSupport { let result: string[] = []; if (this._richEditBrackets) { - for (let i = 0, len = this._richEditBrackets.brackets.length; i < len; i++) { - let bracketPair = this._richEditBrackets.brackets[i]; - let lastChar = bracketPair.close.charAt(bracketPair.close.length - 1); - result.push(lastChar); + for (const bracket of this._richEditBrackets.brackets) { + for (const close of bracket.close) { + const lastChar = close.charAt(close.length - 1); + result.push(lastChar); + } } } @@ -56,7 +57,7 @@ export class BracketElectricCharacterSupport { let reversedBracketRegex = this._richEditBrackets.reversedRegex; let text = context.getLineContent().substring(0, column - 1) + character; - let r = BracketsUtils.findPrevBracketInToken(reversedBracketRegex, 1, text, 0, text.length); + let r = BracketsUtils.findPrevBracketInRange(reversedBracketRegex, 1, text, 0, text.length); if (!r) { return null; } diff --git a/src/vs/editor/common/modes/supports/richEditBrackets.ts b/src/vs/editor/common/modes/supports/richEditBrackets.ts index 0eb595037d..c13665b3c2 100644 --- a/src/vs/editor/common/modes/supports/richEditBrackets.ts +++ b/src/vs/editor/common/modes/supports/richEditBrackets.ts @@ -8,27 +8,107 @@ import { Range } from 'vs/editor/common/core/range'; import { LanguageIdentifier } from 'vs/editor/common/modes'; import { CharacterPair } from 'vs/editor/common/modes/languageConfiguration'; -interface ISimpleInternalBracket { - open: string; - close: string; +interface InternalBracket { + open: string[]; + close: string[]; } export class RichEditBracket { _richEditBracketBrand: void; readonly languageIdentifier: LanguageIdentifier; - readonly open: string; - readonly close: string; + readonly index: number; + readonly open: string[]; + readonly close: string[]; readonly forwardRegex: RegExp; readonly reversedRegex: RegExp; + private readonly _openSet: Set; + private readonly _closeSet: Set; - constructor(languageIdentifier: LanguageIdentifier, open: string, close: string, forwardRegex: RegExp, reversedRegex: RegExp) { + constructor(languageIdentifier: LanguageIdentifier, index: number, open: string[], close: string[], forwardRegex: RegExp, reversedRegex: RegExp) { this.languageIdentifier = languageIdentifier; + this.index = index; this.open = open; this.close = close; this.forwardRegex = forwardRegex; this.reversedRegex = reversedRegex; + this._openSet = RichEditBracket._toSet(this.open); + this._closeSet = RichEditBracket._toSet(this.close); } + + public isOpen(text: string) { + return this._openSet.has(text); + } + + public isClose(text: string) { + return this._closeSet.has(text); + } + + private static _toSet(arr: string[]): Set { + const result = new Set(); + for (const element of arr) { + result.add(element); + } + return result; + } +} + +function groupFuzzyBrackets(brackets: CharacterPair[]): InternalBracket[] { + const N = brackets.length; + + brackets = brackets.map(b => [b[0].toLowerCase(), b[1].toLowerCase()]); + + const group: number[] = []; + for (let i = 0; i < N; i++) { + group[i] = i; + } + + const areOverlapping = (a: CharacterPair, b: CharacterPair) => { + const [aOpen, aClose] = a; + const [bOpen, bClose] = b; + return (aOpen === bOpen || aOpen === bClose || aClose === bOpen || aClose === bClose); + }; + + const mergeGroups = (g1: number, g2: number) => { + const newG = Math.min(g1, g2); + const oldG = Math.max(g1, g2); + for (let i = 0; i < N; i++) { + if (group[i] === oldG) { + group[i] = newG; + } + } + }; + + // group together brackets that have the same open or the same close sequence + for (let i = 0; i < N; i++) { + const a = brackets[i]; + for (let j = i + 1; j < N; j++) { + const b = brackets[j]; + if (areOverlapping(a, b)) { + mergeGroups(group[i], group[j]); + } + } + } + + const result: InternalBracket[] = []; + for (let g = 0; g < N; g++) { + let currentOpen: string[] = []; + let currentClose: string[] = []; + for (let i = 0; i < N; i++) { + if (group[i] === g) { + const [open, close] = brackets[i]; + currentOpen.push(open); + currentClose.push(close); + } + } + if (currentOpen.length > 0) { + result.push({ + open: currentOpen, + close: currentClose + }); + } + } + return result; } export class RichEditBrackets { @@ -41,87 +121,140 @@ export class RichEditBrackets { public readonly textIsBracket: { [text: string]: RichEditBracket; }; public readonly textIsOpenBracket: { [text: string]: boolean; }; - constructor(languageIdentifier: LanguageIdentifier, brackets: CharacterPair[]) { - this.brackets = brackets.map((b) => { + constructor(languageIdentifier: LanguageIdentifier, _brackets: CharacterPair[]) { + const brackets = groupFuzzyBrackets(_brackets); + + this.brackets = brackets.map((b, index) => { return new RichEditBracket( languageIdentifier, - b[0], - b[1], - getRegexForBracketPair({ open: b[0], close: b[1] }), - getReversedRegexForBracketPair({ open: b[0], close: b[1] }) + index, + b.open, + b.close, + getRegexForBracketPair(b.open, b.close, brackets, index), + getReversedRegexForBracketPair(b.open, b.close, brackets, index) ); }); + this.forwardRegex = getRegexForBrackets(this.brackets); this.reversedRegex = getReversedRegexForBrackets(this.brackets); this.textIsBracket = {}; this.textIsOpenBracket = {}; - let maxBracketLength = 0; - this.brackets.forEach((b) => { - this.textIsBracket[b.open.toLowerCase()] = b; - this.textIsBracket[b.close.toLowerCase()] = b; - this.textIsOpenBracket[b.open.toLowerCase()] = true; - this.textIsOpenBracket[b.close.toLowerCase()] = false; - maxBracketLength = Math.max(maxBracketLength, b.open.length); - maxBracketLength = Math.max(maxBracketLength, b.close.length); - }); - this.maxBracketLength = maxBracketLength; - } -} - -function once(keyFn: (input: T) => string, computeFn: (input: T) => R): (input: T) => R { - let cache: { [key: string]: R; } = {}; - return (input: T): R => { - let key = keyFn(input); - if (!cache.hasOwnProperty(key)) { - cache[key] = computeFn(input); + this.maxBracketLength = 0; + for (const bracket of this.brackets) { + for (const open of bracket.open) { + this.textIsBracket[open] = bracket; + this.textIsOpenBracket[open] = true; + this.maxBracketLength = Math.max(this.maxBracketLength, open.length); + } + for (const close of bracket.close) { + this.textIsBracket[close] = bracket; + this.textIsOpenBracket[close] = false; + this.maxBracketLength = Math.max(this.maxBracketLength, close.length); + } } - return cache[key]; - }; + } } -const getRegexForBracketPair = once( - (input) => `${input.open};${input.close}`, - (input) => { - return createBracketOrRegExp([input.open, input.close]); +function collectSuperstrings(str: string, brackets: InternalBracket[], currentIndex: number, dest: string[]): void { + for (let i = 0, len = brackets.length; i < len; i++) { + if (i === currentIndex) { + continue; + } + const bracket = brackets[i]; + for (const open of bracket.open) { + if (open.indexOf(str) >= 0) { + dest.push(open); + } + } + for (const close of bracket.close) { + if (close.indexOf(str) >= 0) { + dest.push(close); + } + } } -); +} -const getReversedRegexForBracketPair = once( - (input) => `${input.open};${input.close}`, - (input) => { - return createBracketOrRegExp([toReversedString(input.open), toReversedString(input.close)]); - } -); +function lengthcmp(a: string, b: string) { + return a.length - b.length; +} -const getRegexForBrackets = once( - (input) => input.map(b => `${b.open};${b.close}`).join(';'), - (input) => { - let pieces: string[] = []; - input.forEach((b) => { - pieces.push(b.open); - pieces.push(b.close); - }); - return createBracketOrRegExp(pieces); +function unique(arr: string[]): string[] { + if (arr.length <= 1) { + return arr; } -); + const result: string[] = []; + const seen = new Set(); + for (const element of arr) { + if (seen.has(element)) { + continue; + } + result.push(element); + seen.add(element); + } + return result; +} -const getReversedRegexForBrackets = once( - (input) => input.map(b => `${b.open};${b.close}`).join(';'), - (input) => { - let pieces: string[] = []; - input.forEach((b) => { - pieces.push(toReversedString(b.open)); - pieces.push(toReversedString(b.close)); - }); - return createBracketOrRegExp(pieces); +function getRegexForBracketPair(open: string[], close: string[], brackets: InternalBracket[], currentIndex: number): RegExp { + // search in all brackets for other brackets that are a superstring of these brackets + let pieces: string[] = []; + pieces = pieces.concat(open); + pieces = pieces.concat(close); + for (let i = 0, len = pieces.length; i < len; i++) { + collectSuperstrings(pieces[i], brackets, currentIndex, pieces); } -); + pieces = unique(pieces); + pieces.sort(lengthcmp); + pieces.reverse(); + return createBracketOrRegExp(pieces); +} + +function getReversedRegexForBracketPair(open: string[], close: string[], brackets: InternalBracket[], currentIndex: number): RegExp { + // search in all brackets for other brackets that are a superstring of these brackets + let pieces: string[] = []; + pieces = pieces.concat(open); + pieces = pieces.concat(close); + for (let i = 0, len = pieces.length; i < len; i++) { + collectSuperstrings(pieces[i], brackets, currentIndex, pieces); + } + pieces = unique(pieces); + pieces.sort(lengthcmp); + pieces.reverse(); + return createBracketOrRegExp(pieces.map(toReversedString)); +} + +function getRegexForBrackets(brackets: RichEditBracket[]): RegExp { + let pieces: string[] = []; + for (const bracket of brackets) { + for (const open of bracket.open) { + pieces.push(open); + } + for (const close of bracket.close) { + pieces.push(close); + } + } + pieces = unique(pieces); + return createBracketOrRegExp(pieces); +} + +function getReversedRegexForBrackets(brackets: RichEditBracket[]): RegExp { + let pieces: string[] = []; + for (const bracket of brackets) { + for (const open of bracket.open) { + pieces.push(open); + } + for (const close of bracket.close) { + pieces.push(close); + } + } + pieces = unique(pieces); + return createBracketOrRegExp(pieces.map(toReversedString)); +} function prepareBracketForRegExp(str: string): string { // This bracket pair uses letters like e.g. "begin" - "end" - const insertWordBoundaries = (/^[\w]+$/.test(str)); + const insertWordBoundaries = (/^[\w ]+$/.test(str)); str = strings.escapeRegExpCharacters(str); return (insertWordBoundaries ? `\\b${str}\\b` : str); } @@ -168,12 +301,11 @@ export class BracketsUtils { return new Range(lineNumber, absoluteMatchOffset - matchLength + 1, lineNumber, absoluteMatchOffset + 1); } - public static findPrevBracketInToken(reversedBracketRegex: RegExp, lineNumber: number, lineText: string, currentTokenStart: number, currentTokenEnd: number): Range | null { + public static findPrevBracketInRange(reversedBracketRegex: RegExp, lineNumber: number, lineText: string, startOffset: number, endOffset: number): Range | null { // Because JS does not support backwards regex search, we search forwards in a reversed string with a reversed regex ;) - let reversedLineText = toReversedString(lineText); - let reversedTokenText = reversedLineText.substring(lineText.length - currentTokenEnd, lineText.length - currentTokenStart); - - return this._findPrevBracketInText(reversedBracketRegex, lineNumber, reversedTokenText, currentTokenStart); + const reversedLineText = toReversedString(lineText); + const reversedSubstr = reversedLineText.substring(lineText.length - endOffset, lineText.length - startOffset); + return this._findPrevBracketInText(reversedBracketRegex, lineNumber, reversedSubstr, startOffset); } public static findNextBracketInText(bracketRegex: RegExp, lineNumber: number, text: string, offset: number): Range | null { @@ -193,10 +325,8 @@ export class BracketsUtils { return new Range(lineNumber, absoluteMatchOffset + 1, lineNumber, absoluteMatchOffset + 1 + matchLength); } - public static findNextBracketInToken(bracketRegex: RegExp, lineNumber: number, lineText: string, currentTokenStart: number, currentTokenEnd: number): Range | null { - let currentTokenText = lineText.substring(currentTokenStart, currentTokenEnd); - - return this.findNextBracketInText(bracketRegex, lineNumber, currentTokenText, currentTokenStart); + public static findNextBracketInRange(bracketRegex: RegExp, lineNumber: number, lineText: string, startOffset: number, endOffset: number): Range | null { + const substr = lineText.substring(startOffset, endOffset); + return this.findNextBracketInText(bracketRegex, lineNumber, substr, startOffset); } - } diff --git a/src/vs/editor/common/modes/textToHtmlTokenizer.ts b/src/vs/editor/common/modes/textToHtmlTokenizer.ts index 2bfb6570dd..f9988db015 100644 --- a/src/vs/editor/common/modes/textToHtmlTokenizer.ts +++ b/src/vs/editor/common/modes/textToHtmlTokenizer.ts @@ -24,7 +24,7 @@ export function tokenizeToString(text: string, tokenizationSupport: IReducedToke return _tokenizeToString(text, tokenizationSupport || fallback); } -export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens, colorMap: string[], startOffset: number, endOffset: number, tabSize: number): string { +export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens, colorMap: string[], startOffset: number, endOffset: number, tabSize: number, useNbsp: boolean): string { let result = `
`; let charIndex = startOffset; let tabsCharDelta = 0; @@ -46,7 +46,7 @@ export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize; tabsCharDelta += insertSpacesCount - 1; while (insertSpacesCount > 0) { - partContent += ' '; + partContent += useNbsp ? ' ' : ' '; insertSpacesCount--; } break; @@ -78,7 +78,7 @@ export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens break; case CharCode.Space: - partContent += ' '; + partContent += useNbsp ? ' ' : ' '; break; default: diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index fad3f67d89..94e101d93c 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -377,11 +377,11 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { // ---- BEGIN diff -------------------------------------------------------------------------- - public computeDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean): Promise { + public async computeDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise { const original = this._getModel(originalUrl); const modified = this._getModel(modifiedUrl); if (!original || !modified) { - return Promise.resolve(null); + return null; } const originalLines = original.getLinesContent(); @@ -390,15 +390,17 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { shouldComputeCharChanges: true, shouldPostProcessCharChanges: true, shouldIgnoreTrimWhitespace: ignoreTrimWhitespace, - shouldMakePrettyDiff: true + shouldMakePrettyDiff: true, + maxComputationTime: maxComputationTime }); - const changes = diffComputer.computeDiff(); - let identical = (changes.length > 0 ? false : this._modelsAreIdentical(original, modified)); - return Promise.resolve({ + const diffResult = diffComputer.computeDiff(); + const identical = (diffResult.changes.length > 0 ? false : this._modelsAreIdentical(original, modified)); + return { + quitEarly: diffResult.quitEarly, identical: identical, - changes: changes - }); + changes: diffResult.changes + }; } private _modelsAreIdentical(original: ICommonModel, modified: ICommonModel): boolean { @@ -417,11 +419,11 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { return true; } - public computeDirtyDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean): Promise { + public async computeDirtyDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean): Promise { let original = this._getModel(originalUrl); let modified = this._getModel(modifiedUrl); if (!original || !modified) { - return Promise.resolve(null); + return null; } let originalLines = original.getLinesContent(); @@ -430,9 +432,10 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { shouldComputeCharChanges: false, shouldPostProcessCharChanges: false, shouldIgnoreTrimWhitespace: ignoreTrimWhitespace, - shouldMakePrettyDiff: true + shouldMakePrettyDiff: true, + maxComputationTime: 1000 }); - return Promise.resolve(diffComputer.computeDiff()); + return diffComputer.computeDiff().changes; } // ---- END diff -------------------------------------------------------------------------- @@ -442,10 +445,10 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { private static readonly _diffLimit = 100000; - public computeMoreMinimalEdits(modelUrl: string, edits: TextEdit[]): Promise { + public async computeMoreMinimalEdits(modelUrl: string, edits: TextEdit[]): Promise { const model = this._getModel(modelUrl); if (!model) { - return Promise.resolve(edits); + return edits; } const result: TextEdit[] = []; @@ -508,28 +511,28 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { result.push({ eol: lastEol, text: '', range: { startLineNumber: 0, startColumn: 0, endLineNumber: 0, endColumn: 0 } }); } - return Promise.resolve(result); + return result; } // ---- END minimal edits --------------------------------------------------------------- - public computeLinks(modelUrl: string): Promise { + public async computeLinks(modelUrl: string): Promise { let model = this._getModel(modelUrl); if (!model) { - return Promise.resolve(null); + return null; } - return Promise.resolve(computeLinks(model)); + return computeLinks(model); } // ---- BEGIN suggest -------------------------------------------------------------------------- private static readonly _suggestionsLimit = 10000; - public textualSuggest(modelUrl: string, position: IPosition, wordDef: string, wordDefFlags: string): Promise { + public async textualSuggest(modelUrl: string, position: IPosition, wordDef: string, wordDefFlags: string): Promise { const model = this._getModel(modelUrl); if (!model) { - return Promise.resolve(null); + return null; } const seen: Record = Object.create(null); @@ -563,7 +566,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { range: { startLineNumber: position.lineNumber, startColumn: wordUntil.startColumn, endLineNumber: position.lineNumber, endColumn: wordUntil.endColumn } }); } - return Promise.resolve({ suggestions }); + return { suggestions }; } @@ -571,10 +574,10 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { //#region -- word ranges -- - computeWordRanges(modelUrl: string, range: IRange, wordDef: string, wordDefFlags: string): Promise<{ [word: string]: IRange[] }> { + public async computeWordRanges(modelUrl: string, range: IRange, wordDef: string, wordDefFlags: string): Promise<{ [word: string]: IRange[] }> { let model = this._getModel(modelUrl); if (!model) { - return Promise.resolve(Object.create(null)); + return Object.create(null); } const wordDefRegExp = new RegExp(wordDef, wordDefFlags); const result: { [word: string]: IRange[] } = Object.create(null); @@ -597,15 +600,15 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { }); } } - return Promise.resolve(result); + return result; } //#endregion - public navigateValueSet(modelUrl: string, range: IRange, up: boolean, wordDef: string, wordDefFlags: string): Promise { + public async navigateValueSet(modelUrl: string, range: IRange, up: boolean, wordDef: string, wordDefFlags: string): Promise { let model = this._getModel(modelUrl); if (!model) { - return Promise.resolve(null); + return null; } let wordDefRegExp = new RegExp(wordDef, wordDefFlags); @@ -623,11 +626,11 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { let wordRange = model.getWordAtPosition({ lineNumber: range.startLineNumber, column: range.startColumn }, wordDefRegExp); if (!wordRange) { - return Promise.resolve(null); + return null; } let word = model.getValueInRange(wordRange); let result = BasicInplaceReplace.INSTANCE.navigateValueSet(range, selectionText, wordRange, word, up); - return Promise.resolve(result); + return result; } // ---- BEGIN foreign module support -------------------------------------------------------------------------- diff --git a/src/vs/editor/common/services/editorWorkerService.ts b/src/vs/editor/common/services/editorWorkerService.ts index d1b4b0829f..fe6b8710f6 100644 --- a/src/vs/editor/common/services/editorWorkerService.ts +++ b/src/vs/editor/common/services/editorWorkerService.ts @@ -13,6 +13,7 @@ export const ID_EDITOR_WORKER_SERVICE = 'editorWorkerService'; export const IEditorWorkerService = createDecorator(ID_EDITOR_WORKER_SERVICE); export interface IDiffComputationResult { + quitEarly: boolean; identical: boolean; changes: ILineChange[]; } @@ -21,7 +22,7 @@ export interface IEditorWorkerService { _serviceBrand: undefined; canComputeDiff(original: URI, modified: URI): boolean; - computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise; + computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise; canComputeDirtyDiff(original: URI, modified: URI): boolean; computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise; diff --git a/src/vs/editor/common/services/editorWorkerServiceImpl.ts b/src/vs/editor/common/services/editorWorkerServiceImpl.ts index fb44928dfe..bd362844ab 100644 --- a/src/vs/editor/common/services/editorWorkerServiceImpl.ts +++ b/src/vs/editor/common/services/editorWorkerServiceImpl.ts @@ -82,8 +82,8 @@ export class EditorWorkerServiceImpl extends Disposable implements IEditorWorker return (canSyncModel(this._modelService, original) && canSyncModel(this._modelService, modified)); } - public computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise { - return this._workerManager.withWorker().then(client => client.computeDiff(original, modified, ignoreTrimWhitespace)); + public computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise { + return this._workerManager.withWorker().then(client => client.computeDiff(original, modified, ignoreTrimWhitespace, maxComputationTime)); } public canComputeDirtyDiff(original: URI, modified: URI): boolean { @@ -409,9 +409,9 @@ export class EditorWorkerClient extends Disposable { }); } - public computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise { + public computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise { return this._withSyncedResources([original, modified]).then(proxy => { - return proxy.computeDiff(original.toString(), modified.toString(), ignoreTrimWhitespace); + return proxy.computeDiff(original.toString(), modified.toString(), ignoreTrimWhitespace, maxComputationTime); }); } diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 049e6879b6..772ed30880 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -218,6 +218,10 @@ export class ModelServiceImpl extends Disposable implements IModelService { } private static _setModelOptionsForModel(model: ITextModel, newOptions: ITextModelCreationOptions, currentOptions: ITextModelCreationOptions): void { + if (currentOptions && currentOptions.defaultEOL !== newOptions.defaultEOL && model.getLineCount() === 1) { + model.setEOL(newOptions.defaultEOL === DefaultEndOfLine.LF ? EndOfLineSequence.LF : EndOfLineSequence.CRLF); + } + if (currentOptions && (currentOptions.detectIndentation === newOptions.detectIndentation) && (currentOptions.insertSpaces === newOptions.insertSpaces) diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index dd85ebebe0..5f771526d3 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -403,10 +403,8 @@ export enum TextEditorCursorStyle { export enum RenderMinimap { None = 0, - Small = 1, - Large = 2, - SmallBlocks = 3, - LargeBlocks = 4 + Text = 1, + Blocks = 2 } export enum RenderLineNumbersType { diff --git a/src/vs/editor/common/view/minimapCharRenderer.ts b/src/vs/editor/common/view/minimapCharRenderer.ts deleted file mode 100644 index 71faf09d31..0000000000 --- a/src/vs/editor/common/view/minimapCharRenderer.ts +++ /dev/null @@ -1,349 +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 { Emitter, Event } from 'vs/base/common/event'; -import { RGBA8 } from 'vs/editor/common/core/rgba'; -import { ColorId, TokenizationRegistry } from 'vs/editor/common/modes'; - -export class MinimapTokensColorTracker { - private static _INSTANCE: MinimapTokensColorTracker | null = null; - public static getInstance(): MinimapTokensColorTracker { - if (!this._INSTANCE) { - this._INSTANCE = new MinimapTokensColorTracker(); - } - return this._INSTANCE; - } - - private _colors!: RGBA8[]; - private _backgroundIsLight!: boolean; - - private readonly _onDidChange = new Emitter(); - public readonly onDidChange: Event = this._onDidChange.event; - - private constructor() { - this._updateColorMap(); - TokenizationRegistry.onDidChange((e) => { - if (e.changedColorMap) { - this._updateColorMap(); - } - }); - } - - private _updateColorMap(): void { - const colorMap = TokenizationRegistry.getColorMap(); - if (!colorMap) { - this._colors = [RGBA8.Empty]; - this._backgroundIsLight = true; - return; - } - this._colors = [RGBA8.Empty]; - for (let colorId = 1; colorId < colorMap.length; colorId++) { - const source = colorMap[colorId].rgba; - // Use a VM friendly data-type - this._colors[colorId] = new RGBA8(source.r, source.g, source.b, Math.round(source.a * 255)); - } - let backgroundLuminosity = colorMap[ColorId.DefaultBackground].getRelativeLuminance(); - this._backgroundIsLight = (backgroundLuminosity >= 0.5); - this._onDidChange.fire(undefined); - } - - public getColor(colorId: ColorId): RGBA8 { - if (colorId < 1 || colorId >= this._colors.length) { - // background color (basically invisible) - colorId = ColorId.DefaultBackground; - } - return this._colors[colorId]; - } - - public backgroundIsLight(): boolean { - return this._backgroundIsLight; - } -} - -export const enum Constants { - START_CH_CODE = 32, // Space - END_CH_CODE = 126, // Tilde (~) - CHAR_COUNT = END_CH_CODE - START_CH_CODE + 1, - - SAMPLED_CHAR_HEIGHT = 16, - SAMPLED_CHAR_WIDTH = 10, - SAMPLED_HALF_CHAR_WIDTH = SAMPLED_CHAR_WIDTH / 2, - - x2_CHAR_HEIGHT = 4, - x2_CHAR_WIDTH = 2, - - x1_CHAR_HEIGHT = 2, - x1_CHAR_WIDTH = 1, - - RGBA_CHANNELS_CNT = 4, -} - -export class MinimapCharRenderer { - - _minimapCharRendererBrand: void; - - public readonly x2charData: Uint8ClampedArray; - public readonly x1charData: Uint8ClampedArray; - - public readonly x2charDataLight: Uint8ClampedArray; - public readonly x1charDataLight: Uint8ClampedArray; - - constructor(x2CharData: Uint8ClampedArray, x1CharData: Uint8ClampedArray) { - const x2ExpectedLen = Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH * Constants.CHAR_COUNT; - if (x2CharData.length !== x2ExpectedLen) { - throw new Error('Invalid x2CharData'); - } - const x1ExpectedLen = Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH * Constants.CHAR_COUNT; - if (x1CharData.length !== x1ExpectedLen) { - throw new Error('Invalid x1CharData'); - } - this.x2charData = x2CharData; - this.x1charData = x1CharData; - - this.x2charDataLight = MinimapCharRenderer.soften(x2CharData, 12 / 15); - this.x1charDataLight = MinimapCharRenderer.soften(x1CharData, 50 / 60); - } - - private static soften(input: Uint8ClampedArray, ratio: number): Uint8ClampedArray { - let result = new Uint8ClampedArray(input.length); - for (let i = 0, len = input.length; i < len; i++) { - result[i] = input[i] * ratio; - } - return result; - } - - private static _getChIndex(chCode: number): number { - chCode -= Constants.START_CH_CODE; - if (chCode < 0) { - chCode += Constants.CHAR_COUNT; - } - return (chCode % Constants.CHAR_COUNT); - } - - public x2RenderChar(target: ImageData, dx: number, dy: number, chCode: number, color: RGBA8, backgroundColor: RGBA8, useLighterFont: boolean): void { - if (dx + Constants.x2_CHAR_WIDTH > target.width || dy + Constants.x2_CHAR_HEIGHT > target.height) { - console.warn('bad render request outside image data'); - return; - } - const x2CharData = useLighterFont ? this.x2charDataLight : this.x2charData; - const chIndex = MinimapCharRenderer._getChIndex(chCode); - - const outWidth = target.width * Constants.RGBA_CHANNELS_CNT; - - const backgroundR = backgroundColor.r; - const backgroundG = backgroundColor.g; - const backgroundB = backgroundColor.b; - - const deltaR = color.r - backgroundR; - const deltaG = color.g - backgroundG; - const deltaB = color.b - backgroundB; - - const dest = target.data; - const sourceOffset = chIndex * Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH; - let destOffset = dy * outWidth + dx * Constants.RGBA_CHANNELS_CNT; - { - const c = x2CharData[sourceOffset] / 255; - dest[destOffset + 0] = backgroundR + deltaR * c; - dest[destOffset + 1] = backgroundG + deltaG * c; - dest[destOffset + 2] = backgroundB + deltaB * c; - } - { - const c = x2CharData[sourceOffset + 1] / 255; - dest[destOffset + 4] = backgroundR + deltaR * c; - dest[destOffset + 5] = backgroundG + deltaG * c; - dest[destOffset + 6] = backgroundB + deltaB * c; - } - - destOffset += outWidth; - { - const c = x2CharData[sourceOffset + 2] / 255; - dest[destOffset + 0] = backgroundR + deltaR * c; - dest[destOffset + 1] = backgroundG + deltaG * c; - dest[destOffset + 2] = backgroundB + deltaB * c; - } - { - const c = x2CharData[sourceOffset + 3] / 255; - dest[destOffset + 4] = backgroundR + deltaR * c; - dest[destOffset + 5] = backgroundG + deltaG * c; - dest[destOffset + 6] = backgroundB + deltaB * c; - } - - destOffset += outWidth; - { - const c = x2CharData[sourceOffset + 4] / 255; - dest[destOffset + 0] = backgroundR + deltaR * c; - dest[destOffset + 1] = backgroundG + deltaG * c; - dest[destOffset + 2] = backgroundB + deltaB * c; - } - { - const c = x2CharData[sourceOffset + 5] / 255; - dest[destOffset + 4] = backgroundR + deltaR * c; - dest[destOffset + 5] = backgroundG + deltaG * c; - dest[destOffset + 6] = backgroundB + deltaB * c; - } - - destOffset += outWidth; - { - const c = x2CharData[sourceOffset + 6] / 255; - dest[destOffset + 0] = backgroundR + deltaR * c; - dest[destOffset + 1] = backgroundG + deltaG * c; - dest[destOffset + 2] = backgroundB + deltaB * c; - } - { - const c = x2CharData[sourceOffset + 7] / 255; - dest[destOffset + 4] = backgroundR + deltaR * c; - dest[destOffset + 5] = backgroundG + deltaG * c; - dest[destOffset + 6] = backgroundB + deltaB * c; - } - } - - public x1RenderChar(target: ImageData, dx: number, dy: number, chCode: number, color: RGBA8, backgroundColor: RGBA8, useLighterFont: boolean): void { - if (dx + Constants.x1_CHAR_WIDTH > target.width || dy + Constants.x1_CHAR_HEIGHT > target.height) { - console.warn('bad render request outside image data'); - return; - } - const x1CharData = useLighterFont ? this.x1charDataLight : this.x1charData; - const chIndex = MinimapCharRenderer._getChIndex(chCode); - - const outWidth = target.width * Constants.RGBA_CHANNELS_CNT; - - const backgroundR = backgroundColor.r; - const backgroundG = backgroundColor.g; - const backgroundB = backgroundColor.b; - - const deltaR = color.r - backgroundR; - const deltaG = color.g - backgroundG; - const deltaB = color.b - backgroundB; - - const dest = target.data; - const sourceOffset = chIndex * Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH; - let destOffset = dy * outWidth + dx * Constants.RGBA_CHANNELS_CNT; - { - const c = x1CharData[sourceOffset] / 255; - dest[destOffset + 0] = backgroundR + deltaR * c; - dest[destOffset + 1] = backgroundG + deltaG * c; - dest[destOffset + 2] = backgroundB + deltaB * c; - } - - destOffset += outWidth; - { - const c = x1CharData[sourceOffset + 1] / 255; - dest[destOffset + 0] = backgroundR + deltaR * c; - dest[destOffset + 1] = backgroundG + deltaG * c; - dest[destOffset + 2] = backgroundB + deltaB * c; - } - } - - public x2BlockRenderChar(target: ImageData, dx: number, dy: number, color: RGBA8, backgroundColor: RGBA8, useLighterFont: boolean): void { - if (dx + Constants.x2_CHAR_WIDTH > target.width || dy + Constants.x2_CHAR_HEIGHT > target.height) { - console.warn('bad render request outside image data'); - return; - } - - const outWidth = target.width * Constants.RGBA_CHANNELS_CNT; - - const c = 0.5; - - const backgroundR = backgroundColor.r; - const backgroundG = backgroundColor.g; - const backgroundB = backgroundColor.b; - - const deltaR = color.r - backgroundR; - const deltaG = color.g - backgroundG; - const deltaB = color.b - backgroundB; - - const colorR = backgroundR + deltaR * c; - const colorG = backgroundG + deltaG * c; - const colorB = backgroundB + deltaB * c; - - const dest = target.data; - let destOffset = dy * outWidth + dx * Constants.RGBA_CHANNELS_CNT; - { - dest[destOffset + 0] = colorR; - dest[destOffset + 1] = colorG; - dest[destOffset + 2] = colorB; - } - { - dest[destOffset + 4] = colorR; - dest[destOffset + 5] = colorG; - dest[destOffset + 6] = colorB; - } - - destOffset += outWidth; - { - dest[destOffset + 0] = colorR; - dest[destOffset + 1] = colorG; - dest[destOffset + 2] = colorB; - } - { - dest[destOffset + 4] = colorR; - dest[destOffset + 5] = colorG; - dest[destOffset + 6] = colorB; - } - - destOffset += outWidth; - { - dest[destOffset + 0] = colorR; - dest[destOffset + 1] = colorG; - dest[destOffset + 2] = colorB; - } - { - dest[destOffset + 4] = colorR; - dest[destOffset + 5] = colorG; - dest[destOffset + 6] = colorB; - } - - destOffset += outWidth; - { - dest[destOffset + 0] = colorR; - dest[destOffset + 1] = colorG; - dest[destOffset + 2] = colorB; - } - { - dest[destOffset + 4] = colorR; - dest[destOffset + 5] = colorG; - dest[destOffset + 6] = colorB; - } - } - - public x1BlockRenderChar(target: ImageData, dx: number, dy: number, color: RGBA8, backgroundColor: RGBA8, useLighterFont: boolean): void { - if (dx + Constants.x1_CHAR_WIDTH > target.width || dy + Constants.x1_CHAR_HEIGHT > target.height) { - console.warn('bad render request outside image data'); - return; - } - - const outWidth = target.width * Constants.RGBA_CHANNELS_CNT; - - const c = 0.5; - - const backgroundR = backgroundColor.r; - const backgroundG = backgroundColor.g; - const backgroundB = backgroundColor.b; - - const deltaR = color.r - backgroundR; - const deltaG = color.g - backgroundG; - const deltaB = color.b - backgroundB; - - const colorR = backgroundR + deltaR * c; - const colorG = backgroundG + deltaG * c; - const colorB = backgroundB + deltaB * c; - - const dest = target.data; - - let destOffset = dy * outWidth + dx * Constants.RGBA_CHANNELS_CNT; - { - dest[destOffset + 0] = colorR; - dest[destOffset + 1] = colorG; - dest[destOffset + 2] = colorB; - } - - destOffset += outWidth; - { - dest[destOffset + 0] = colorR; - dest[destOffset + 1] = colorG; - dest[destOffset + 2] = colorB; - } - } -} diff --git a/src/vs/editor/common/view/runtimeMinimapCharRenderer.ts b/src/vs/editor/common/view/runtimeMinimapCharRenderer.ts deleted file mode 100644 index 0c9c956ff9..0000000000 --- a/src/vs/editor/common/view/runtimeMinimapCharRenderer.ts +++ /dev/null @@ -1,985 +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 { MinimapCharRenderer } from 'vs/editor/common/view/minimapCharRenderer'; - -function toUint8ClampedArrat(arr: number[]): Uint8ClampedArray { - let r = new Uint8ClampedArray(arr.length); - for (let i = 0, len = arr.length; i < len; i++) { - r[i] = arr[i]; - } - return r; -} - -let minimapCharRenderer: MinimapCharRenderer | null = null; -export function getOrCreateMinimapCharRenderer(): MinimapCharRenderer { - if (!minimapCharRenderer) { - let _x1Data = toUint8ClampedArrat(x1Data!); - x1Data = null; - - let _x2Data = toUint8ClampedArrat(x2Data!); - x2Data = null; - minimapCharRenderer = new MinimapCharRenderer(_x2Data, _x1Data); - } - return minimapCharRenderer; -} - -let x2Data: number[] | null = [ - - // - 0, 0, - 0, 0, - 0, 0, - 0, 0, - - // ! - 39, 14, - 39, 14, - 14, 5, - 29, 10, - - // " - 96, 96, - 29, 29, - 0, 0, - 0, 0, - - // # - 49, 113, - 195, 214, - 227, 166, - 135, 42, - - // $ - 40, 29, - 194, 38, - 75, 148, - 197, 187, - - // % - 145, 0, - 160, 61, - 75, 143, - 2, 183, - - // & - 138, 58, - 163, 6, - 177, 223, - 197, 227, - - // ' - 38, 13, - 11, 4, - 0, 0, - 0, 0, - - // ( - 10, 54, - 52, 8, - 62, 4, - 71, 122, - - // ) - 73, 2, - 19, 40, - 10, 50, - 155, 36, - - // * - 79, 70, - 145, 121, - 7, 5, - 0, 0, - - // + - 2, 1, - 36, 12, - 204, 166, - 16, 5, - - // , - 0, 0, - 0, 0, - 1, 0, - 154, 34, - - // - - 0, 0, - 0, 0, - 96, 83, - 0, 0, - - // . - 0, 0, - 0, 0, - 0, 0, - 46, 34, - - // / - 0, 82, - 2, 56, - 53, 3, - 146, 0, - - // 0 - 146, 119, - 152, 132, - 152, 131, - 145, 119, - - // 1 - 170, 42, - 15, 42, - 15, 42, - 172, 194, - - // 2 - 131, 132, - 0, 139, - 80, 28, - 227, 143, - - // 3 - 159, 135, - 15, 118, - 11, 126, - 171, 144, - - // 4 - 20, 124, - 88, 106, - 217, 196, - 0, 106, - - // 5 - 189, 92, - 168, 43, - 5, 130, - 164, 133, - - // 6 - 130, 115, - 183, 65, - 134, 120, - 141, 141, - - // 7 - 170, 196, - 2, 106, - 31, 32, - 105, 2, - - // 8 - 145, 130, - 116, 114, - 132, 135, - 138, 140, - - // 9 - 138, 113, - 147, 137, - 81, 183, - 129, 94, - - // : - 0, 0, - 21, 16, - 4, 3, - 46, 34, - - // ; - 0, 0, - 45, 34, - 1, 0, - 160, 49, - - // < - 0, 0, - 43, 143, - 203, 23, - 1, 76, - - // = - 0, 0, - 38, 28, - 131, 96, - 38, 28, - - // > - 0, 0, - 168, 31, - 29, 191, - 98, 0, - - // ? - 118, 139, - 5, 113, - 45, 13, - 37, 6, - - // @ - 97, 115, - 161, 179, - 204, 105, - 223, 224, - - // A - 83, 52, - 111, 100, - 184, 186, - 120, 132, - - // B - 212, 145, - 180, 139, - 174, 161, - 212, 182, - - // C - 104, 162, - 131, 0, - 131, 0, - 104, 161, - - // D - 219, 120, - 110, 116, - 110, 116, - 219, 120, - - // E - 207, 154, - 163, 40, - 147, 22, - 207, 154, - - // F - 202, 159, - 161, 47, - 145, 23, - 111, 0, - - // G - 139, 154, - 144, 30, - 144, 135, - 139, 187, - - // H - 110, 110, - 168, 161, - 150, 145, - 110, 110, - - // I - 185, 162, - 43, 16, - 43, 16, - 185, 162, - - // J - 73, 129, - 0, 110, - 0, 110, - 191, 87, - - // K - 149, 149, - 236, 48, - 195, 91, - 146, 149, - - // L - 146, 0, - 146, 0, - 146, 0, - 187, 173, - - // M - 200, 201, - 222, 215, - 172, 147, - 95, 95, - - // N - 193, 97, - 224, 129, - 159, 206, - 97, 192, - - // O - 155, 139, - 153, 115, - 153, 115, - 156, 140, - - // P - 189, 158, - 123, 136, - 190, 64, - 111, 0, - - // Q - 155, 139, - 153, 115, - 153, 114, - 156, 241, - - // R - 197, 148, - 150, 152, - 170, 116, - 110, 157, - - // S - 156, 128, - 169, 14, - 13, 159, - 158, 149, - - // T - 212, 189, - 43, 16, - 43, 16, - 43, 16, - - // U - 148, 110, - 148, 110, - 147, 109, - 182, 151, - - // V - 133, 121, - 106, 118, - 114, 103, - 89, 66, - - // W - 94, 94, - 211, 188, - 205, 207, - 139, 168, - - // X - 151, 152, - 87, 76, - 101, 79, - 151, 152, - - // Y - 130, 156, - 125, 116, - 47, 29, - 43, 16, - - // Z - 169, 228, - 11, 103, - 120, 6, - 230, 176, - - // [ - 55, 49, - 55, 6, - 55, 6, - 193, 102, - - // \ - 92, 0, - 71, 0, - 13, 30, - 0, 147, - - // ] - 63, 43, - 12, 43, - 12, 43, - 142, 152, - - // ^ - 71, 53, - 61, 61, - 0, 0, - 0, 0, - - // _ - 0, 0, - 0, 0, - 0, 0, - 158, 146, - - // ` - 25, 2, - 0, 0, - 0, 0, - 0, 0, - - // a - 0, 0, - 107, 130, - 170, 194, - 176, 188, - - // b - 109, 0, - 203, 159, - 113, 111, - 202, 158, - - // c - 0, 0, - 135, 135, - 114, 0, - 136, 135, - - // d - 0, 109, - 187, 190, - 148, 126, - 177, 187, - - // e - 0, 0, - 149, 130, - 218, 105, - 169, 135, - - // f - 37, 113, - 146, 113, - 49, 13, - 49, 13, - - // g - 0, 0, - 178, 195, - 147, 114, - 255, 255, - - // h - 109, 0, - 193, 149, - 110, 109, - 109, 109, - - // i - 12, 15, - 125, 41, - 33, 41, - 144, 188, - - // j - 1, 6, - 75, 53, - 10, 53, - 210, 161, - - // k - 110, 0, - 152, 148, - 210, 60, - 110, 156, - - // l - 213, 5, - 63, 5, - 63, 5, - 45, 111, - - // m - 0, 0, - 232, 172, - 190, 168, - 190, 169, - - // n - 0, 0, - 190, 144, - 109, 109, - 109, 109, - - // o - 0, 0, - 168, 140, - 148, 111, - 168, 140, - - // p - 0, 0, - 200, 151, - 113, 110, - 255, 158, - - // q - 0, 0, - 184, 188, - 147, 139, - 186, 255, - - // r - 0, 0, - 122, 130, - 111, 0, - 109, 0, - - // s - 0, 0, - 132, 69, - 109, 93, - 110, 136, - - // t - 51, 5, - 205, 103, - 61, 6, - 47, 106, - - // u - 0, 0, - 110, 109, - 110, 122, - 155, 179, - - // v - 0, 0, - 132, 120, - 113, 114, - 84, 63, - - // w - 0, 0, - 124, 108, - 202, 189, - 160, 174, - - // x - 0, 0, - 144, 142, - 79, 57, - 159, 146, - - // y - 0, 0, - 138, 138, - 119, 117, - 255, 69, - - // z - 0, 0, - 97, 198, - 47, 38, - 208, 84, - - // { - 23, 112, - 41, 14, - 157, 7, - 121, 192, - - // | - 35, 11, - 35, 11, - 35, 11, - 160, 61, - - // } - 129, 9, - 40, 19, - 20, 139, - 236, 44, - - // ~ - 0, 0, - 15, 3, - 97, 93, - 0, 0, - -]; - -let x1Data: number[] | null = [ - - // - 0, - 0, - - // ! - 23, - 12, - - // " - 53, - 0, - - // # - 130, - 127, - - // $ - 58, - 149, - - // % - 67, - 77, - - // & - 72, - 198, - - // ' - 13, - 0, - - // ( - 25, - 51, - - // ) - 25, - 49, - - // * - 94, - 2, - - // + - 8, - 64, - - // , - 0, - 24, - - // - - 0, - 21, - - // . - 0, - 9, - - // / - 19, - 27, - - // 0 - 126, - 126, - - // 1 - 51, - 80, - - // 2 - 72, - 105, - - // 3 - 87, - 98, - - // 4 - 73, - 93, - - // 5 - 106, - 85, - - // 6 - 111, - 123, - - // 7 - 87, - 30, - - // 8 - 116, - 126, - - // 9 - 123, - 110, - - // : - 4, - 16, - - // ; - 9, - 28, - - // < - 21, - 53, - - // = - 8, - 62, - - // > - 23, - 52, - - // ? - 73, - 21, - - // @ - 132, - 183, - - // A - 78, - 142, - - // B - 168, - 175, - - // C - 70, - 70, - - // D - 128, - 128, - - // E - 123, - 110, - - // F - 125, - 43, - - // G - 100, - 139, - - // H - 125, - 119, - - // I - 78, - 78, - - // J - 54, - 77, - - // K - 139, - 139, - - // L - 33, - 87, - - // M - 201, - 117, - - // N - 162, - 149, - - // O - 130, - 130, - - // P - 138, - 60, - - // Q - 130, - 172, - - // R - 149, - 127, - - // S - 95, - 98, - - // T - 95, - 25, - - // U - 118, - 135, - - // V - 110, - 85, - - // W - 147, - 175, - - // X - 105, - 110, - - // Y - 121, - 30, - - // Z - 101, - 113, - - // [ - 34, - 68, - - // \ - 20, - 26, - - // ] - 34, - 68, - - // ^ - 56, - 0, - - // _ - 0, - 44, - - // ` - 3, - 0, - - // a - 27, - 175, - - // b - 80, - 133, - - // c - 31, - 66, - - // d - 85, - 147, - - // e - 32, - 150, - - // f - 90, - 25, - - // g - 45, - 230, - - // h - 77, - 101, - - // i - 36, - 83, - - // j - 22, - 84, - - // k - 71, - 118, - - // l - 44, - 44, - - // m - 52, - 172, - - // n - 38, - 101, - - // o - 35, - 130, - - // p - 40, - 197, - - // q - 43, - 197, - - // r - 29, - 26, - - // s - 23, - 103, - - // t - 67, - 44, - - // u - 25, - 129, - - // v - 29, - 85, - - // w - 27, - 177, - - // x - 33, - 97, - - // y - 32, - 145, - - // z - 33, - 77, - - // { - 38, - 96, - - // | - 20, - 55, - - // } - 36, - 95, - - // ~ - 2, - 22, - -]; diff --git a/src/vs/editor/common/viewLayout/lineDecorations.ts b/src/vs/editor/common/viewLayout/lineDecorations.ts index 3dfa13a841..e316cc2608 100644 --- a/src/vs/editor/common/viewLayout/lineDecorations.ts +++ b/src/vs/editor/common/viewLayout/lineDecorations.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as strings from 'vs/base/common/strings'; -import { Constants } from 'vs/editor/common/core/uint'; +import { Constants } from 'vs/base/common/uint'; import { InlineDecoration, InlineDecorationType } from 'vs/editor/common/viewModel/viewModel'; export class LineDecoration { diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index 591ae35c19..df96d613ab 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -7,7 +7,7 @@ import { CharCode } from 'vs/base/common/charCode'; import * as strings from 'vs/base/common/strings'; import { WrappingIndent } from 'vs/editor/common/config/editorOptions'; import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier'; -import { toUint32Array } from 'vs/editor/common/core/uint'; +import { toUint32Array } from 'vs/base/common/uint'; import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; import { ILineMapperFactory, ILineMapping, OutputPosition } from 'vs/editor/common/viewModel/splitLinesCollection'; diff --git a/src/vs/editor/common/viewModel/minimapTokensColorTracker.ts b/src/vs/editor/common/viewModel/minimapTokensColorTracker.ts new file mode 100644 index 0000000000..ee51fad644 --- /dev/null +++ b/src/vs/editor/common/viewModel/minimapTokensColorTracker.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 { Emitter, Event } from 'vs/base/common/event'; +import { RGBA8 } from 'vs/editor/common/core/rgba'; +import { ColorId, TokenizationRegistry } from 'vs/editor/common/modes'; + +export class MinimapTokensColorTracker { + private static _INSTANCE: MinimapTokensColorTracker | null = null; + public static getInstance(): MinimapTokensColorTracker { + if (!this._INSTANCE) { + this._INSTANCE = new MinimapTokensColorTracker(); + } + return this._INSTANCE; + } + + private _colors!: RGBA8[]; + private _backgroundIsLight!: boolean; + + private readonly _onDidChange = new Emitter(); + public readonly onDidChange: Event = this._onDidChange.event; + + private constructor() { + this._updateColorMap(); + TokenizationRegistry.onDidChange(e => { + if (e.changedColorMap) { + this._updateColorMap(); + } + }); + } + + private _updateColorMap(): void { + const colorMap = TokenizationRegistry.getColorMap(); + if (!colorMap) { + this._colors = [RGBA8.Empty]; + this._backgroundIsLight = true; + return; + } + this._colors = [RGBA8.Empty]; + for (let colorId = 1; colorId < colorMap.length; colorId++) { + const source = colorMap[colorId].rgba; + // Use a VM friendly data-type + this._colors[colorId] = new RGBA8(source.r, source.g, source.b, Math.round(source.a * 255)); + } + let backgroundLuminosity = colorMap[ColorId.DefaultBackground].getRelativeLuminance(); + this._backgroundIsLight = backgroundLuminosity >= 0.5; + this._onDidChange.fire(undefined); + } + + public getColor(colorId: ColorId): RGBA8 { + if (colorId < 1 || colorId >= this._colors.length) { + // background color (basically invisible) + colorId = ColorId.DefaultBackground; + } + return this._colors[colorId]; + } + + public backgroundIsLight(): boolean { + return this._backgroundIsLight; + } +} diff --git a/src/vs/editor/common/viewModel/prefixSumComputer.ts b/src/vs/editor/common/viewModel/prefixSumComputer.ts index 2f06c6fccf..a0e9eec728 100644 --- a/src/vs/editor/common/viewModel/prefixSumComputer.ts +++ b/src/vs/editor/common/viewModel/prefixSumComputer.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { toUint32 } from 'vs/editor/common/core/uint'; +import { toUint32 } from 'vs/base/common/uint'; export class PrefixSumIndexOfResult { _prefixSumIndexOfResultBrand: void; diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index b236398437..4a2cd356ad 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -15,7 +15,7 @@ import { ModelDecorationOverviewRulerOptions, ModelDecorationMinimapOptions } fr import * as textModelEvents from 'vs/editor/common/model/textModelEvents'; import { ColorId, LanguageId, TokenizationRegistry } from 'vs/editor/common/modes'; import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer'; -import { MinimapTokensColorTracker } from 'vs/editor/common/view/minimapCharRenderer'; +import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTokensColorTracker'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { ViewLayout } from 'vs/editor/common/viewLayout/viewLayout'; import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; @@ -24,6 +24,7 @@ import { ICoordinatesConverter, IOverviewRulerDecorations, IViewModel, MinimapLi import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations'; import { ITheme } from 'vs/platform/theme/common/themeService'; import { RunOnceScheduler } from 'vs/base/common/async'; +import * as platform from 'vs/base/common/platform'; const USE_IDENTITY_LINES_COLLECTION = true; @@ -128,6 +129,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel super.dispose(); this.decorations.dispose(); this.lines.dispose(); + this.invalidateMinimapColorCache(); this.viewportStartLineTrackedRange = this.model._setTrackedRange(this.viewportStartLineTrackedRange, null, TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges); } @@ -626,9 +628,19 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel ranges = ranges.slice(0); ranges.sort(Range.compareRangesUsingStarts); - const nonEmptyRanges = ranges.filter((r) => !r.isEmpty()); - if (nonEmptyRanges.length === 0) { + let hasEmptyRange = false; + let hasNonEmptyRange = false; + for (const range of ranges) { + if (range.isEmpty()) { + hasEmptyRange = true; + } else { + hasNonEmptyRange = true; + } + } + + if (!hasNonEmptyRange) { + // all ranges are empty if (!emptySelectionClipboard) { return ''; } @@ -648,9 +660,29 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel return result; } + if (hasEmptyRange && emptySelectionClipboard) { + // mixed empty selections and non-empty selections + let result: string[] = []; + let prevModelLineNumber = 0; + for (const range of ranges) { + const modelLineNumber = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(range.startLineNumber, 1)).lineNumber; + if (range.isEmpty()) { + if (modelLineNumber !== prevModelLineNumber) { + result.push(this.model.getLineContent(modelLineNumber)); + } + } else { + result.push(this.getValueInRange(range, forceCRLF ? EndOfLinePreference.CRLF : EndOfLinePreference.TextDefined)); + } + prevModelLineNumber = modelLineNumber; + } + return result.length === 1 ? result[0] : result; + } + let result: string[] = []; - for (const nonEmptyRange of nonEmptyRanges) { - result.push(this.getValueInRange(nonEmptyRange, forceCRLF ? EndOfLinePreference.CRLF : EndOfLinePreference.TextDefined)); + for (const range of ranges) { + if (!range.isEmpty()) { + result.push(this.getValueInRange(range, forceCRLF ? EndOfLinePreference.CRLF : EndOfLinePreference.TextDefined)); + } } return result.length === 1 ? result[0] : result; } @@ -713,7 +745,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel if (lineContent === '') { result += '
'; } else { - result += tokenizeLineToHTML(lineContent, lineTokens.inflate(), colorMap, startOffset, endOffset, tabSize); + result += tokenizeLineToHTML(lineContent, lineTokens.inflate(), colorMap, startOffset, endOffset, tabSize, platform.isWindows); } } diff --git a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts index 92758738b2..3a1aaf2591 100644 --- a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts +++ b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts @@ -56,16 +56,36 @@ class SelectToBracketAction extends EditorAction { id: 'editor.action.selectToBracket', label: nls.localize('smartSelect.selectToBracket', "Select to Bracket"), alias: 'Select to Bracket', - precondition: undefined + precondition: undefined, + description: { + description: `Select to Bracket`, + args: [{ + name: 'args', + schema: { + type: 'object', + properties: { + 'selectBrackets': { + type: 'boolean', + default: true + } + }, + } + }] + } }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - let controller = BracketMatchingController.get(editor); + public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + const controller = BracketMatchingController.get(editor); if (!controller) { return; } - controller.selectToBracket(); + + let selectBrackets = true; + if (args && args.selectBrackets === false) { + selectBrackets = false; + } + controller.selectToBracket(selectBrackets); } } @@ -82,7 +102,7 @@ class BracketsData { } export class BracketMatchingController extends Disposable implements editorCommon.IEditorContribution { - private static readonly ID = 'editor.contrib.bracketMatchingController'; + public static readonly ID = 'editor.contrib.bracketMatchingController'; public static get(editor: ICodeEditor): BracketMatchingController { return editor.getContribution(BracketMatchingController.ID); @@ -140,10 +160,6 @@ export class BracketMatchingController extends Disposable implements editorCommo })); } - public getId(): string { - return BracketMatchingController.ID; - } - public jumpToBracket(): void { if (!this._editor.hasModel()) { return; @@ -163,10 +179,16 @@ export class BracketMatchingController extends Disposable implements editorCommo newCursorPosition = brackets[0].getStartPosition(); } } else { - // find the next bracket if the position isn't on a matching bracket - const nextBracket = model.findNextBracket(position); - if (nextBracket && nextBracket.range) { - newCursorPosition = nextBracket.range.getStartPosition(); + // find the enclosing brackets if the position isn't on a matching bracket + const enclosingBrackets = model.findEnclosingBrackets(position); + if (enclosingBrackets) { + newCursorPosition = enclosingBrackets[0].getStartPosition(); + } else { + // no enclosing brackets, try the very first next bracket + const nextBracket = model.findNextBracket(position); + if (nextBracket && nextBracket.range) { + newCursorPosition = nextBracket.range.getStartPosition(); + } } } @@ -180,7 +202,7 @@ export class BracketMatchingController extends Disposable implements editorCommo this._editor.revealRange(newSelections[0]); } - public selectToBracket(): void { + public selectToBracket(selectBrackets: boolean): void { if (!this._editor.hasModel()) { return; } @@ -192,36 +214,31 @@ export class BracketMatchingController extends Disposable implements editorCommo const position = selection.getStartPosition(); let brackets = model.matchBracket(position); - let openBracket: Position | null = null; - let closeBracket: Position | null = null; - if (!brackets) { - const nextBracket = model.findNextBracket(position); - if (nextBracket && nextBracket.range) { - brackets = model.matchBracket(nextBracket.range.getStartPosition()); + brackets = model.findEnclosingBrackets(position); + if (!brackets) { + const nextBracket = model.findNextBracket(position); + if (nextBracket && nextBracket.range) { + brackets = model.matchBracket(nextBracket.range.getStartPosition()); + } } } + let selectFrom: Position | null = null; + let selectTo: Position | null = null; + if (brackets) { - if (brackets[0].startLineNumber === brackets[1].startLineNumber) { - openBracket = brackets[1].startColumn < brackets[0].startColumn ? - brackets[1].getStartPosition() : brackets[0].getStartPosition(); - closeBracket = brackets[1].startColumn < brackets[0].startColumn ? - brackets[0].getEndPosition() : brackets[1].getEndPosition(); - } else { - openBracket = brackets[1].startLineNumber < brackets[0].startLineNumber ? - brackets[1].getStartPosition() : brackets[0].getStartPosition(); - closeBracket = brackets[1].startLineNumber < brackets[0].startLineNumber ? - brackets[0].getEndPosition() : brackets[1].getEndPosition(); - } + brackets.sort(Range.compareRangesUsingStarts); + const [open, close] = brackets; + selectFrom = selectBrackets ? open.getStartPosition() : open.getEndPosition(); + selectTo = selectBrackets ? close.getEndPosition() : close.getStartPosition(); } - if (openBracket && closeBracket) { - newSelections.push(new Selection(openBracket.lineNumber, openBracket.column, closeBracket.lineNumber, closeBracket.column)); + if (selectFrom && selectTo) { + newSelections.push(new Selection(selectFrom.lineNumber, selectFrom.column, selectTo.lineNumber, selectTo.column)); } }); - if (newSelections.length > 0) { this._editor.setSelections(newSelections); this._editor.revealRange(newSelections[0]); @@ -264,6 +281,14 @@ export class BracketMatchingController extends Disposable implements editorCommo return; } + const selections = this._editor.getSelections(); + if (selections.length > 100) { + // no bracket matching for high numbers of selections + this._lastBracketsData = []; + this._lastVersionId = 0; + return; + } + const model = this._editor.getModel(); const versionId = model.getVersionId(); let previousData: BracketsData[] = []; @@ -272,8 +297,6 @@ export class BracketMatchingController extends Disposable implements editorCommo previousData = this._lastBracketsData; } - const selections = this._editor.getSelections(); - let positions: Position[] = [], positionsLen = 0; for (let i = 0, len = selections.length; i < len; i++) { let selection = selections[i]; @@ -302,6 +325,9 @@ export class BracketMatchingController extends Disposable implements editorCommo newData[newDataLen++] = previousData[previousIndex]; } else { let brackets = model.matchBracket(position); + if (!brackets) { + brackets = model.findEnclosingBrackets(position); + } newData[newDataLen++] = new BracketsData(position, brackets); } } @@ -311,7 +337,7 @@ export class BracketMatchingController extends Disposable implements editorCommo } } -registerEditorContribution(BracketMatchingController); +registerEditorContribution(BracketMatchingController.ID, BracketMatchingController); registerEditorAction(SelectToBracketAction); registerEditorAction(JumpToBracketAction); registerThemingParticipant((theme, collector) => { diff --git a/src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts b/src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts index ac92a4bcf5..4a5152ad04 100644 --- a/src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts +++ b/src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts @@ -34,7 +34,7 @@ suite('bracket matching', () => { let model = TextModel.createFromString('var x = (3 + (5-7)) + ((5+3)+5);', undefined, mode.getLanguageIdentifier()); withTestCodeEditor(null, { model: model }, (editor, cursor) => { - let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController); + let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); // start on closing bracket editor.setPosition(new Position(1, 20)); @@ -66,7 +66,7 @@ suite('bracket matching', () => { let model = TextModel.createFromString('var x = (3 + (5-7)); y();', undefined, mode.getLanguageIdentifier()); withTestCodeEditor(null, { model: model }, (editor, cursor) => { - let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController); + let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); // start position between brackets editor.setPosition(new Position(1, 16)); @@ -103,36 +103,36 @@ suite('bracket matching', () => { let model = TextModel.createFromString('var x = (3 + (5-7)); y();', undefined, mode.getLanguageIdentifier()); withTestCodeEditor(null, { model: model }, (editor, cursor) => { - let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController); + let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); // start position in open brackets editor.setPosition(new Position(1, 9)); - bracketMatchingController.selectToBracket(); + bracketMatchingController.selectToBracket(true); assert.deepEqual(editor.getPosition(), new Position(1, 20)); assert.deepEqual(editor.getSelection(), new Selection(1, 9, 1, 20)); // start position in close brackets editor.setPosition(new Position(1, 20)); - bracketMatchingController.selectToBracket(); + bracketMatchingController.selectToBracket(true); assert.deepEqual(editor.getPosition(), new Position(1, 20)); assert.deepEqual(editor.getSelection(), new Selection(1, 9, 1, 20)); // start position between brackets editor.setPosition(new Position(1, 16)); - bracketMatchingController.selectToBracket(); + bracketMatchingController.selectToBracket(true); assert.deepEqual(editor.getPosition(), new Position(1, 19)); assert.deepEqual(editor.getSelection(), new Selection(1, 14, 1, 19)); // start position outside brackets editor.setPosition(new Position(1, 21)); - bracketMatchingController.selectToBracket(); + bracketMatchingController.selectToBracket(true); assert.deepEqual(editor.getPosition(), new Position(1, 25)); assert.deepEqual(editor.getSelection(), new Selection(1, 23, 1, 25)); // do not break if no brackets are available editor.setPosition(new Position(1, 26)); - bracketMatchingController.selectToBracket(); + bracketMatchingController.selectToBracket(true); assert.deepEqual(editor.getPosition(), new Position(1, 26)); assert.deepEqual(editor.getSelection(), new Selection(1, 26, 1, 26)); @@ -143,12 +143,62 @@ suite('bracket matching', () => { mode.dispose(); }); + test('issue #1772: jump to enclosing brackets', () => { + const text = [ + 'const x = {', + ' something: [0, 1, 2],', + ' another: true,', + ' somethingmore: [0, 2, 4]', + '};', + ].join('\n'); + const mode = new BracketMode(); + const model = TextModel.createFromString(text, undefined, mode.getLanguageIdentifier()); + + withTestCodeEditor(null, { model: model }, (editor, cursor) => { + const bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); + + editor.setPosition(new Position(3, 5)); + bracketMatchingController.jumpToBracket(); + assert.deepEqual(editor.getSelection(), new Selection(5, 1, 5, 1)); + + bracketMatchingController.dispose(); + }); + + model.dispose(); + mode.dispose(); + }); + + test('issue #43371: argument to not select brackets', () => { + const text = [ + 'const x = {', + ' something: [0, 1, 2],', + ' another: true,', + ' somethingmore: [0, 2, 4]', + '};', + ].join('\n'); + const mode = new BracketMode(); + const model = TextModel.createFromString(text, undefined, mode.getLanguageIdentifier()); + + withTestCodeEditor(null, { model: model }, (editor, cursor) => { + const bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); + + editor.setPosition(new Position(3, 5)); + bracketMatchingController.selectToBracket(false); + assert.deepEqual(editor.getSelection(), new Selection(1, 12, 5, 1)); + + bracketMatchingController.dispose(); + }); + + model.dispose(); + mode.dispose(); + }); + test('issue #45369: Select to Bracket with multicursor', () => { let mode = new BracketMode(); let model = TextModel.createFromString('{ } { } { }', undefined, mode.getLanguageIdentifier()); withTestCodeEditor(null, { model: model }, (editor, cursor) => { - let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController); + let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); // cursors inside brackets become selections of the entire bracket contents editor.setSelections([ @@ -156,7 +206,7 @@ suite('bracket matching', () => { new Selection(1, 10, 1, 10), new Selection(1, 17, 1, 17) ]); - bracketMatchingController.selectToBracket(); + bracketMatchingController.selectToBracket(true); assert.deepEqual(editor.getSelections(), [ new Selection(1, 1, 1, 5), new Selection(1, 8, 1, 13), @@ -169,7 +219,7 @@ suite('bracket matching', () => { new Selection(1, 6, 1, 6), new Selection(1, 14, 1, 14) ]); - bracketMatchingController.selectToBracket(); + bracketMatchingController.selectToBracket(true); assert.deepEqual(editor.getSelections(), [ new Selection(1, 1, 1, 5), new Selection(1, 8, 1, 13), @@ -182,7 +232,7 @@ suite('bracket matching', () => { new Selection(1, 13, 1, 13), new Selection(1, 19, 1, 19) ]); - bracketMatchingController.selectToBracket(); + bracketMatchingController.selectToBracket(true); assert.deepEqual(editor.getSelections(), [ new Selection(1, 1, 1, 5), new Selection(1, 8, 1, 13), diff --git a/src/vs/editor/contrib/codeAction/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/codeActionCommands.ts index 2fe9d1e7bc..71902363c6 100644 --- a/src/vs/editor/contrib/codeAction/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/codeActionCommands.ts @@ -5,6 +5,7 @@ import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { Lazy } from 'vs/base/common/lazy'; import { Disposable } from 'vs/base/common/lifecycle'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -39,7 +40,7 @@ function contextKeyForSupportedActions(kind: CodeActionKind) { export class QuickFixController extends Disposable implements IEditorContribution { - private static readonly ID = 'editor.contrib.quickFixController'; + public static readonly ID = 'editor.contrib.quickFixController'; public static get(editor: ICodeEditor): QuickFixController { return editor.getContribution(QuickFixController.ID); @@ -47,7 +48,7 @@ export class QuickFixController extends Disposable implements IEditorContributio private readonly _editor: ICodeEditor; private readonly _model: CodeActionModel; - private readonly _ui: CodeActionUi; + private readonly _ui: Lazy; constructor( editor: ICodeEditor, @@ -64,31 +65,29 @@ export class QuickFixController extends Disposable implements IEditorContributio this._editor = editor; this._model = this._register(new CodeActionModel(this._editor, markerService, contextKeyService, progressService)); - this._register(this._model.onDidChangeState((newState) => this.update(newState))); + this._register(this._model.onDidChangeState(newState => this.update(newState))); - this._ui = this._register(new CodeActionUi(editor, QuickFixAction.Id, { - applyCodeAction: async (action, retrigger) => { - try { - await this._applyCodeAction(action); - } finally { - if (retrigger) { - this._trigger({ type: 'auto', filter: {} }); + this._ui = new Lazy(() => + this._register(new CodeActionUi(editor, QuickFixAction.Id, AutoFixAction.Id, { + applyCodeAction: async (action, retrigger) => { + try { + await this._applyCodeAction(action); + } finally { + if (retrigger) { + this._trigger({ type: 'auto', filter: {} }); + } } } - } - }, contextMenuService, keybindingService)); + }, contextMenuService, keybindingService)) + ); } private update(newState: CodeActionsState.State): void { - this._ui.update(newState); + this._ui.getValue().update(newState); } public showCodeActions(actions: CodeActionSet, at: IAnchor | IPosition) { - return this._ui.showCodeActionList(actions, at); - } - - public getId(): string { - return QuickFixController.ID; + return this._ui.getValue().showCodeActionList(actions, at); } public manualTriggerAtCurrentPosition( diff --git a/src/vs/editor/contrib/codeAction/codeActionContributions.ts b/src/vs/editor/contrib/codeAction/codeActionContributions.ts index b7b5c3562c..595bccef3c 100644 --- a/src/vs/editor/contrib/codeAction/codeActionContributions.ts +++ b/src/vs/editor/contrib/codeAction/codeActionContributions.ts @@ -7,7 +7,7 @@ import { registerEditorAction, registerEditorCommand, registerEditorContribution import { CodeActionCommand, OrganizeImportsAction, QuickFixAction, QuickFixController, RefactorAction, SourceAction, AutoFixAction, FixAllAction } from 'vs/editor/contrib/codeAction/codeActionCommands'; -registerEditorContribution(QuickFixController); +registerEditorContribution(QuickFixController.ID, QuickFixController); registerEditorAction(QuickFixAction); registerEditorAction(RefactorAction); registerEditorAction(SourceAction); diff --git a/src/vs/editor/contrib/codeAction/codeActionUi.ts b/src/vs/editor/contrib/codeAction/codeActionUi.ts index 09e2613844..734f71f502 100644 --- a/src/vs/editor/contrib/codeAction/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/codeActionUi.ts @@ -17,16 +17,18 @@ import { CodeActionWidget } from './codeActionWidget'; import { LightBulbWidget } from './lightBulbWidget'; import { IPosition } from 'vs/editor/common/core/position'; import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; +import { Lazy } from 'vs/base/common/lazy'; export class CodeActionUi extends Disposable { - private readonly _codeActionWidget: CodeActionWidget; - private readonly _lightBulbWidget: LightBulbWidget; + private readonly _codeActionWidget: Lazy; + private readonly _lightBulbWidget: Lazy; private readonly _activeCodeActions = this._register(new MutableDisposable()); constructor( private readonly _editor: ICodeEditor, quickFixActionId: string, + preferredFixActionId: string, private readonly delegate: { applyCodeAction: (action: CodeAction, regtriggerAfterApply: boolean) => void }, @@ -35,19 +37,26 @@ export class CodeActionUi extends Disposable { ) { super(); - this._codeActionWidget = this._register(new CodeActionWidget(this._editor, contextMenuService, { - onSelectCodeAction: async (action) => { - this.delegate.applyCodeAction(action, /* retrigger */ true); - } - })); - this._lightBulbWidget = this._register(new LightBulbWidget(this._editor, quickFixActionId, keybindingService)); + this._codeActionWidget = new Lazy(() => { + return this._register(new CodeActionWidget(this._editor, contextMenuService, { + onSelectCodeAction: async (action) => { + this.delegate.applyCodeAction(action, /* retrigger */ true); + } + })); + }); - this._register(this._lightBulbWidget.onClick(this._handleLightBulbSelect, this)); + this._lightBulbWidget = new Lazy(() => { + const widget = this._register(new LightBulbWidget(this._editor, quickFixActionId, preferredFixActionId, keybindingService)); + this._register(widget.onClick(this._handleLightBulbSelect, this)); + return widget; + }); } public async update(newState: CodeActionsState.State): Promise { if (newState.type !== CodeActionsState.Type.Triggered) { - this._lightBulbWidget.hide(); + if (this._lightBulbWidget.hasValue()) { + this._lightBulbWidget.getValue().hide(); + } return; } @@ -59,7 +68,7 @@ export class CodeActionUi extends Disposable { return; } - this._lightBulbWidget.update(actions, newState.position); + this._lightBulbWidget.getValue().update(actions, newState.position); if (!actions.actions.length && newState.trigger.context) { MessageController.get(this._editor).showMessage(newState.trigger.context.notAvailableMessage, newState.trigger.context.position); @@ -83,10 +92,10 @@ export class CodeActionUi extends Disposable { } } this._activeCodeActions.value = actions; - this._codeActionWidget.show(actions, newState.position); + this._codeActionWidget.getValue().show(actions, newState.position); } else { // auto magically triggered - if (this._codeActionWidget.isVisible) { + if (this._codeActionWidget.getValue().isVisible) { // TODO: Figure out if we should update the showing menu? actions.dispose(); } else { @@ -96,10 +105,10 @@ export class CodeActionUi extends Disposable { } public async showCodeActionList(actions: CodeActionSet, at?: IAnchor | IPosition): Promise { - this._codeActionWidget.show(actions, at); + this._codeActionWidget.getValue().show(actions, at); } private _handleLightBulbSelect(e: { x: number, y: number, actions: CodeActionSet }): void { - this._codeActionWidget.show(e.actions, e); + this._codeActionWidget.getValue().show(e.actions, e); } } diff --git a/src/vs/editor/contrib/codeAction/codeActionWidget.ts b/src/vs/editor/contrib/codeAction/codeActionWidget.ts index 8a060c19a9..4a922ad895 100644 --- a/src/vs/editor/contrib/codeAction/codeActionWidget.ts +++ b/src/vs/editor/contrib/codeAction/codeActionWidget.ts @@ -21,7 +21,7 @@ interface CodeActionWidgetDelegate { export class CodeActionWidget extends Disposable { - private _visible: boolean; + private _visible: boolean = false; private readonly _showingActions = this._register(new MutableDisposable()); constructor( @@ -30,7 +30,6 @@ export class CodeActionWidget extends Disposable { private readonly _delegate: CodeActionWidgetDelegate, ) { super(); - this._visible = false; } public async show(codeActions: CodeActionSet, at?: IAnchor | IPosition): Promise { diff --git a/src/vs/editor/contrib/codeAction/lightBulbWidget.css b/src/vs/editor/contrib/codeAction/lightBulbWidget.css index 08ab8d9715..fb361a1676 100644 --- a/src/vs/editor/contrib/codeAction/lightBulbWidget.css +++ b/src/vs/editor/contrib/codeAction/lightBulbWidget.css @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +.monaco-editor .lightbulb-glyph, .monaco-editor .codicon-lightbulb { display: flex; align-items: center; @@ -12,6 +13,7 @@ padding-left: 2px; } +.monaco-editor .lightbulb-glyph:hover, .monaco-editor .codicon-lightbulb:hover { cursor: pointer; /* transform: scale(1.3, 1.3); */ diff --git a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts index e6e3387f7e..2e0d2d85b1 100644 --- a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts +++ b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts @@ -47,7 +47,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget { private readonly _domNode: HTMLDivElement; - private readonly _onClick = this._register(new Emitter<{ x: number; y: number; actions: CodeActionSet }>()); + private readonly _onClick = this._register(new Emitter<{ x: number; y: number; actions: CodeActionSet; }>()); public readonly onClick = this._onClick.event; private _state: LightBulbState.State = LightBulbState.Hidden; @@ -55,6 +55,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget { constructor( private readonly _editor: ICodeEditor, private readonly _quickFixActionId: string, + private readonly _preferredFixActionId: string, @IKeybindingService private readonly _keybindingService: IKeybindingService ) { super(); @@ -66,12 +67,12 @@ export class LightBulbWidget extends Disposable implements IContentWidget { this._register(this._editor.onDidChangeModelContent(_ => { // cancel when the line in question has been removed const editorModel = this._editor.getModel(); - if (this._state.type !== LightBulbState.Type.Showing || !editorModel || this._state.editorPosition.lineNumber >= editorModel.getLineCount()) { + if (this.state.type !== LightBulbState.Type.Showing || !editorModel || this.state.editorPosition.lineNumber >= editorModel.getLineCount()) { this.hide(); } })); this._register(dom.addStandardDisposableListener(this._domNode, 'mousedown', e => { - if (this._state.type !== LightBulbState.Type.Showing) { + if (this.state.type !== LightBulbState.Type.Showing) { return; } @@ -84,14 +85,14 @@ export class LightBulbWidget extends Disposable implements IContentWidget { const lineHeight = this._editor.getOption(EditorOption.lineHeight); let pad = Math.floor(lineHeight / 3); - if (this._state.widgetPosition.position !== null && this._state.widgetPosition.position.lineNumber < this._state.editorPosition.lineNumber) { + if (this.state.widgetPosition.position !== null && this.state.widgetPosition.position.lineNumber < this.state.editorPosition.lineNumber) { pad += lineHeight; } this._onClick.fire({ x: e.posx, y: top + height + pad, - actions: this._state.actions + actions: this.state.actions }); })); this._register(dom.addDisposableListener(this._domNode, 'mouseenter', (e: MouseEvent) => { @@ -173,7 +174,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget { } } - this._state = new LightBulbState.Showing(actions, atPosition, { + this.state = new LightBulbState.Showing(actions, atPosition, { position: { lineNumber: effectiveLineNumber, column: 1 }, preference: LightBulbWidget._posPref }); @@ -181,24 +182,37 @@ export class LightBulbWidget extends Disposable implements IContentWidget { this._editor.layoutContentWidget(this); } - private set title(value: string) { - this._domNode.title = value; - } - public hide(): void { - this._state = LightBulbState.Hidden; + this.state = LightBulbState.Hidden; this._editor.layoutContentWidget(this); } + private get state(): LightBulbState.State { return this._state; } + + private set state(value) { + this._state = value; + this._updateLightBulbTitle(); + } + private _updateLightBulbTitle(): void { - const kb = this._keybindingService.lookupKeybinding(this._quickFixActionId); - let title: string; - if (kb) { - title = nls.localize('quickFixWithKb', "Show Fixes ({0})", kb.getLabel()); - } else { - title = nls.localize('quickFix', "Show Fixes"); + if (this.state.type === LightBulbState.Type.Showing && this.state.actions.hasAutoFix) { + const preferredKb = this._keybindingService.lookupKeybinding(this._preferredFixActionId); + if (preferredKb) { + this.title = nls.localize('prefferedQuickFixWithKb', "Show Fixes. Preferred Fix Available ({0})", preferredKb.getLabel()); + return; + } } - this.title = title; + + const kb = this._keybindingService.lookupKeybinding(this._quickFixActionId); + if (kb) { + this.title = nls.localize('quickFixWithKb', "Show Fixes ({0})", kb.getLabel()); + } else { + this.title = nls.localize('quickFix', "Show Fixes"); + } + } + + private set title(value: string) { + this._domNode.title = value; } } diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index 16ce8b5f46..5e0ef96870 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -21,7 +21,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; export class CodeLensContribution implements editorCommon.IEditorContribution { - private static readonly ID: string = 'css.editor.codeLens'; + public static readonly ID: string = 'css.editor.codeLens'; private _isEnabled: boolean; @@ -78,10 +78,6 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { dispose(this._currentCodeLensModel); } - getId(): string { - return CodeLensContribution.ID; - } - private _onModelChange(): void { this._localDispose(); @@ -366,4 +362,4 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { } } -registerEditorContribution(CodeLensContribution); +registerEditorContribution(CodeLensContribution.ID, CodeLensContribution); diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index a99c7fd82c..faacba4fd9 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -6,7 +6,7 @@ import 'vs/css!./codelensWidget'; import * as dom from 'vs/base/browser/dom'; import { coalesce, isFalsyOrEmpty } from 'vs/base/common/arrays'; -import { escape } from 'vs/base/common/strings'; +import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; 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'; @@ -104,7 +104,7 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { for (let i = 0; i < symbols.length; i++) { const command = symbols[i].command; if (command) { - const title = escape(command.title); + const title = renderCodicons(command.title); let part: string; if (command.id) { part = `${title}`; diff --git a/src/vs/editor/contrib/colorPicker/colorDetector.ts b/src/vs/editor/contrib/colorPicker/colorDetector.ts index a1d7685a0d..d361de9ed7 100644 --- a/src/vs/editor/contrib/colorPicker/colorDetector.ts +++ b/src/vs/editor/contrib/colorPicker/colorDetector.ts @@ -25,9 +25,9 @@ const MAX_DECORATORS = 500; export class ColorDetector extends Disposable implements IEditorContribution { - private static readonly ID: string = 'editor.contrib.colorDetector'; + public static readonly ID: string = 'editor.contrib.colorDetector'; - static RECOMPUTE_TIME = 1000; // ms + static readonly RECOMPUTE_TIME = 1000; // ms private readonly _localToDispose = this._register(new DisposableStore()); private _computePromise: CancelablePromise | null; @@ -88,10 +88,6 @@ export class ColorDetector extends Disposable implements IEditorContribution { return this._editor.getOption(EditorOption.colorDecorators); } - getId(): string { - return ColorDetector.ID; - } - static get(editor: ICodeEditor): ColorDetector { return editor.getContribution(this.ID); } @@ -247,4 +243,4 @@ export class ColorDetector extends Disposable implements IEditorContribution { } } -registerEditorContribution(ColorDetector); +registerEditorContribution(ColorDetector.ID, ColorDetector); diff --git a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts index 3368b5ace7..29bb526c5f 100644 --- a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts @@ -32,7 +32,7 @@ export class ColorPickerHeader extends Disposable { this.pickedColorNode = dom.append(this.domNode, $('.picked-color')); const colorBox = dom.append(this.domNode, $('.original-color')); - colorBox.style.backgroundColor = Color.Format.CSS.format(this.model.originalColor); + colorBox.style.backgroundColor = Color.Format.CSS.format(this.model.originalColor) || ''; this.backgroundColor = themeService.getTheme().getColor(editorHoverBackground) || Color.white; this._register(registerThemingParticipant((theme, collector) => { @@ -46,12 +46,12 @@ export class ColorPickerHeader extends Disposable { })); this._register(model.onDidChangeColor(this.onDidChangeColor, this)); this._register(model.onDidChangePresentation(this.onDidChangePresentation, this)); - this.pickedColorNode.style.backgroundColor = Color.Format.CSS.format(model.color); + this.pickedColorNode.style.backgroundColor = Color.Format.CSS.format(model.color) || ''; dom.toggleClass(this.pickedColorNode, 'light', model.color.rgba.a < 0.5 ? this.backgroundColor.isLighter() : model.color.isLighter()); } private onDidChangeColor(color: Color): void { - this.pickedColorNode.style.backgroundColor = Color.Format.CSS.format(color); + this.pickedColorNode.style.backgroundColor = Color.Format.CSS.format(color) || ''; dom.toggleClass(this.pickedColorNode, 'light', color.rgba.a < 0.5 ? this.backgroundColor.isLighter() : color.isLighter()); this.onDidChangePresentation(); } diff --git a/src/vs/editor/contrib/comment/lineCommentCommand.ts b/src/vs/editor/contrib/comment/lineCommentCommand.ts index 6e3273b7e8..f8f08ffd3e 100644 --- a/src/vs/editor/contrib/comment/lineCommentCommand.ts +++ b/src/vs/editor/contrib/comment/lineCommentCommand.ts @@ -13,6 +13,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { BlockCommentCommand } from 'vs/editor/contrib/comment/blockCommentCommand'; +import { Constants } from 'vs/base/common/uint'; export interface IInsertionPoint { ignore: boolean; @@ -392,7 +393,7 @@ export class LineCommentCommand implements editorCommon.ICommand { * Adjust insertion points to have them vertically aligned in the add line comment case */ public static _normalizeInsertionPoint(model: ISimpleModel, lines: IInsertionPoint[], startLineNumber: number, tabSize: number): void { - let minVisibleColumn = Number.MAX_VALUE; + let minVisibleColumn = Constants.MAX_SAFE_SMALL_INTEGER; let j: number; let lenJ: number; diff --git a/src/vs/editor/contrib/contextmenu/contextmenu.ts b/src/vs/editor/contrib/contextmenu/contextmenu.ts index e03c476c39..f219f43ce1 100644 --- a/src/vs/editor/contrib/contextmenu/contextmenu.ts +++ b/src/vs/editor/contrib/contextmenu/contextmenu.ts @@ -26,7 +26,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; export class ContextMenuController implements IEditorContribution { - private static readonly ID = 'editor.contrib.contextmenu'; + public static readonly ID = 'editor.contrib.contextmenu'; public static get(editor: ICodeEditor): ContextMenuController { return editor.getContribution(ContextMenuController.ID); @@ -90,8 +90,18 @@ export class ContextMenuController implements IEditorContribution { this._editor.focus(); // Ensure the cursor is at the position of the mouse click - if (e.target.position && !this._editor.getSelection().containsPosition(e.target.position)) { - this._editor.setPosition(e.target.position); + if (e.target.position) { + let hasSelectionAtPosition = false; + for (const selection of this._editor.getSelections()) { + if (selection.containsPosition(e.target.position)) { + hasSelectionAtPosition = true; + break; + } + } + + if (!hasSelectionAtPosition) { + this._editor.setPosition(e.target.position); + } } // Unless the user triggerd the context menu through Shift+F10, use the mouse position as menu position @@ -209,10 +219,6 @@ export class ContextMenuController implements IEditorContribution { return this._keybindingService.lookupKeybinding(action.id); } - public getId(): string { - return ContextMenuController.ID; - } - public dispose(): void { if (this._contextMenuIsBeingShownCount > 0) { this._contextViewService.hideContextView(); @@ -244,5 +250,5 @@ class ShowContextMenu extends EditorAction { } } -registerEditorContribution(ContextMenuController); +registerEditorContribution(ContextMenuController.ID, ContextMenuController); registerEditorAction(ShowContextMenu); diff --git a/src/vs/editor/contrib/cursorUndo/cursorUndo.ts b/src/vs/editor/contrib/cursorUndo/cursorUndo.ts index c49e6d223b..d9f35bc5a3 100644 --- a/src/vs/editor/contrib/cursorUndo/cursorUndo.ts +++ b/src/vs/editor/contrib/cursorUndo/cursorUndo.ts @@ -35,81 +35,83 @@ class CursorState { } } -export class CursorUndoController extends Disposable implements IEditorContribution { +export class CursorUndoRedoController extends Disposable implements IEditorContribution { - private static readonly ID = 'editor.contrib.cursorUndoController'; + public static readonly ID = 'editor.contrib.cursorUndoRedoController'; - public static get(editor: ICodeEditor): CursorUndoController { - return editor.getContribution(CursorUndoController.ID); + public static get(editor: ICodeEditor): CursorUndoRedoController { + return editor.getContribution(CursorUndoRedoController.ID); } private readonly _editor: ICodeEditor; - private _isCursorUndo: boolean; + private _isCursorUndoRedo: boolean; private _undoStack: CursorState[]; + private _redoStack: CursorState[]; private _prevState: CursorState | null; constructor(editor: ICodeEditor) { super(); this._editor = editor; - this._isCursorUndo = false; + this._isCursorUndoRedo = false; this._undoStack = []; - this._prevState = this._readState(); + this._redoStack = []; + this._prevState = null; this._register(editor.onDidChangeModel((e) => { this._undoStack = []; + this._redoStack = []; this._prevState = null; })); this._register(editor.onDidChangeModelContent((e) => { this._undoStack = []; + this._redoStack = []; this._prevState = null; })); this._register(editor.onDidChangeCursorSelection((e) => { - if (!this._isCursorUndo && this._prevState) { - this._undoStack.push(this._prevState); - if (this._undoStack.length > 50) { - // keep the cursor undo stack bounded - this._undoStack.shift(); + const newState = new CursorState(this._editor.getSelections()!); + + if (!this._isCursorUndoRedo && this._prevState) { + const isEqualToLastUndoStack = (this._undoStack.length > 0 && this._undoStack[this._undoStack.length - 1].equals(this._prevState)); + if (!isEqualToLastUndoStack) { + this._undoStack.push(this._prevState); + this._redoStack = []; + if (this._undoStack.length > 50) { + // keep the cursor undo stack bounded + this._undoStack.shift(); + } } } - this._prevState = this._readState(); + this._prevState = newState; })); } - private _readState(): CursorState | null { - if (!this._editor.hasModel()) { - // no model => no state - return null; - } - - return new CursorState(this._editor.getSelections()); - } - - public getId(): string { - return CursorUndoController.ID; - } - public cursorUndo(): void { - if (!this._editor.hasModel()) { + if (!this._editor.hasModel() || this._undoStack.length === 0) { return; } - const currState = new CursorState(this._editor.getSelections()); + this._redoStack.push(new CursorState(this._editor.getSelections())); + this._applyState(this._undoStack.pop()!); + } - while (this._undoStack.length > 0) { - const prevState = this._undoStack.pop()!; - - if (!prevState.equals(currState)) { - this._isCursorUndo = true; - this._editor.setSelections(prevState.selections); - this._editor.revealRangeInCenterIfOutsideViewport(prevState.selections[0], ScrollType.Smooth); - this._isCursorUndo = false; - return; - } + public cursorRedo(): void { + if (!this._editor.hasModel() || this._redoStack.length === 0) { + return; } + + this._undoStack.push(new CursorState(this._editor.getSelections())); + this._applyState(this._redoStack.pop()!); + } + + private _applyState(state: CursorState): void { + this._isCursorUndoRedo = true; + this._editor.setSelections(state.selections); + this._editor.revealRangeInCenterIfOutsideViewport(state.selections[0], ScrollType.Smooth); + this._isCursorUndoRedo = false; } } @@ -129,9 +131,25 @@ export class CursorUndo extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { - CursorUndoController.get(editor).cursorUndo(); + CursorUndoRedoController.get(editor).cursorUndo(); } } -registerEditorContribution(CursorUndoController); +export class CursorRedo extends EditorAction { + constructor() { + super({ + id: 'cursorRedo', + label: nls.localize('cursor.redo', "Soft Redo"), + alias: 'Soft Redo', + precondition: undefined + }); + } + + public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + CursorUndoRedoController.get(editor).cursorRedo(); + } +} + +registerEditorContribution(CursorUndoRedoController.ID, CursorUndoRedoController); registerEditorAction(CursorUndo); +registerEditorAction(CursorRedo); diff --git a/src/vs/editor/contrib/dnd/dnd.ts b/src/vs/editor/contrib/dnd/dnd.ts index 433b7d0b31..06119ec2c3 100644 --- a/src/vs/editor/contrib/dnd/dnd.ts +++ b/src/vs/editor/contrib/dnd/dnd.ts @@ -31,14 +31,14 @@ function hasTriggerModifier(e: IKeyboardEvent | IMouseEvent): boolean { export class DragAndDropController extends Disposable implements editorCommon.IEditorContribution { - private static readonly ID = 'editor.contrib.dragAndDrop'; + public static readonly ID = 'editor.contrib.dragAndDrop'; private readonly _editor: ICodeEditor; private _dragSelection: Selection | null; private _dndDecorationIds: string[]; private _mouseDown: boolean; private _modifierPressed: boolean; - static TRIGGER_KEY_VALUE = isMacintosh ? KeyCode.Alt : KeyCode.Ctrl; + static readonly TRIGGER_KEY_VALUE = isMacintosh ? KeyCode.Alt : KeyCode.Ctrl; static get(editor: ICodeEditor): DragAndDropController { return editor.getContribution(DragAndDropController.ID); @@ -219,10 +219,6 @@ export class DragAndDropController extends Disposable implements editorCommon.IE target.type === MouseTargetType.GUTTER_LINE_DECORATIONS; } - public getId(): string { - return DragAndDropController.ID; - } - public dispose(): void { this._removeDecoration(); this._dragSelection = null; @@ -232,4 +228,4 @@ export class DragAndDropController extends Disposable implements editorCommon.IE } } -registerEditorContribution(DragAndDropController); +registerEditorContribution(DragAndDropController.ID, DragAndDropController); diff --git a/src/vs/editor/contrib/documentSymbols/media/outlineTree.css b/src/vs/editor/contrib/documentSymbols/media/outlineTree.css index eaee13302b..11c363fcde 100644 --- a/src/vs/editor/contrib/documentSymbols/media/outlineTree.css +++ b/src/vs/editor/contrib/documentSymbols/media/outlineTree.css @@ -35,7 +35,7 @@ } .monaco-list .outline-element .outline-element-decoration.bubble { - font-family: octicons; + font-family: codicon; font-size: 14px; opacity: 0.4; } diff --git a/src/vs/editor/contrib/documentSymbols/outlineTree.ts b/src/vs/editor/contrib/documentSymbols/outlineTree.ts index 8e04fc2938..2eddbe96c4 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineTree.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineTree.ts @@ -6,13 +6,13 @@ import * as dom from 'vs/base/browser/dom'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { IDataSource, ITreeNode, ITreeRenderer, ITreeSorter } from 'vs/base/browser/ui/tree/tree'; +import { IDataSource, ITreeNode, ITreeRenderer, ITreeSorter, ITreeFilter } from 'vs/base/browser/ui/tree/tree'; import { values } from 'vs/base/common/collections'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import 'vs/css!./media/outlineTree'; import 'vs/css!./media/symbol-icons'; import { Range } from 'vs/editor/common/core/range'; -import { SymbolKind, symbolKindToCssClass, SymbolTag } from 'vs/editor/common/modes'; +import { SymbolKind, SymbolKinds, SymbolTag } from 'vs/editor/common/modes'; import { OutlineElement, OutlineGroup, OutlineModel } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { localize } from 'vs/nls'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; @@ -44,7 +44,7 @@ export class OutlineIdentityProvider implements IIdentityProvider { } export class OutlineGroupTemplate { - static id = 'OutlineGroupTemplate'; + static readonly id = 'OutlineGroupTemplate'; constructor( readonly labelContainer: HTMLElement, readonly label: HighlightedLabel, @@ -52,7 +52,7 @@ export class OutlineGroupTemplate { } export class OutlineElementTemplate { - static id = 'OutlineElementTemplate'; + static readonly id = 'OutlineElementTemplate'; constructor( readonly container: HTMLElement, readonly iconLabel: IconLabel, @@ -125,7 +125,7 @@ export class OutlineElementRenderer implements ITreeRenderer= 0) { options.extraClasses.push(`deprecated`); @@ -168,7 +168,7 @@ export class OutlineElementRenderer implements ITreeRenderer { + + private readonly _filteredTypes = new Set(); + + constructor( + private readonly _prefix: string, + @IConfigurationService private readonly _configService: IConfigurationService, + ) { + + } + + update() { + this._filteredTypes.clear(); + for (const name of SymbolKinds.names()) { + if (!this._configService.getValue(`${this._prefix}.${name}`)) { + this._filteredTypes.add(SymbolKinds.fromString(name) || -1); + } + } + } + + filter(element: OutlineItem): boolean { + return !(element instanceof OutlineElement) || !this._filteredTypes.has(element.symbol.kind); + } +} + export class OutlineItemComparator implements ITreeSorter { private readonly _collator = new IdleValue(() => new Intl.Collator(undefined, { numeric: true })); diff --git a/src/vs/editor/contrib/find/findController.ts b/src/vs/editor/contrib/find/findController.ts index f95774acb6..5699df5548 100644 --- a/src/vs/editor/contrib/find/findController.ts +++ b/src/vs/editor/contrib/find/findController.ts @@ -70,7 +70,7 @@ export interface IFindStartOptions { export class CommonFindController extends Disposable implements editorCommon.IEditorContribution { - private static readonly ID = 'editor.contrib.findController'; + public static readonly ID = 'editor.contrib.findController'; protected _editor: ICodeEditor; private readonly _findWidgetVisible: IContextKey; @@ -143,10 +143,6 @@ export class CommonFindController extends Disposable implements editorCommon.IEd } } - public getId(): string { - return CommonFindController.ID; - } - private _onStateChanged(e: FindReplaceStateChangedEvent): void { this.saveQueryState(e); @@ -735,7 +731,7 @@ export class StartFindReplaceAction extends EditorAction { } } -registerEditorContribution(FindController); +registerEditorContribution(CommonFindController.ID, FindController); registerEditorAction(StartFindAction); registerEditorAction(StartFindWithSelectionAction); diff --git a/src/vs/editor/contrib/find/findModel.ts b/src/vs/editor/contrib/find/findModel.ts index 49a181ab87..8c76b84fcb 100644 --- a/src/vs/editor/contrib/find/findModel.ts +++ b/src/vs/editor/contrib/find/findModel.ts @@ -12,7 +12,7 @@ import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/commo import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import { Constants } from 'vs/editor/common/core/uint'; +import { Constants } from 'vs/base/common/uint'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { EndOfLinePreference, FindMatch, ITextModel } from 'vs/editor/common/model'; import { SearchParams } from 'vs/editor/common/model/textModelSearch'; diff --git a/src/vs/editor/contrib/find/test/findController.test.ts b/src/vs/editor/contrib/find/test/findController.test.ts index 0bb7611485..9bfa18156b 100644 --- a/src/vs/editor/contrib/find/test/findController.test.ts +++ b/src/vs/editor/contrib/find/test/findController.test.ts @@ -89,7 +89,7 @@ suite('FindController', () => { assert.ok(true); return; } - let findController = editor.registerAndInstantiateContribution(TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let startFindAction = new StartFindAction(); // I select ABC on the first line editor.setSelection(new Selection(1, 1, 1, 4)); @@ -115,7 +115,7 @@ suite('FindController', () => { return; } - let findController = editor.registerAndInstantiateContribution(TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let findState = findController.getState(); let nextMatchFindAction = new NextMatchFindAction(); @@ -141,7 +141,7 @@ suite('FindController', () => { return; } - let findController = editor.registerAndInstantiateContribution(TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let findState = findController.getState(); findState.change({ searchString: 'ABC' }, true); @@ -161,7 +161,7 @@ suite('FindController', () => { ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; // The cursor is at the very top, of the file, at the first ABC - let findController = editor.registerAndInstantiateContribution(TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let findState = findController.getState(); let startFindAction = new StartFindAction(); let nextMatchFindAction = new NextMatchFindAction(); @@ -215,7 +215,7 @@ suite('FindController', () => { 'import nls = require(\'vs/nls\');' ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; - let findController = editor.registerAndInstantiateContribution(TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let nextMatchFindAction = new NextMatchFindAction(); editor.setPosition({ @@ -240,7 +240,7 @@ suite('FindController', () => { 'var z = (3 * 5)', ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; - let findController = editor.registerAndInstantiateContribution(TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let startFindAction = new StartFindAction(); let nextMatchFindAction = new NextMatchFindAction(); @@ -264,7 +264,7 @@ suite('FindController', () => { 'test', ], { serviceCollection: serviceCollection }, (editor, cursor) => { let testRegexString = 'tes.'; - let findController = editor.registerAndInstantiateContribution(TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let nextMatchFindAction = new NextMatchFindAction(); let startFindReplaceAction = new StartFindReplaceAction(); @@ -294,7 +294,7 @@ suite('FindController', () => { 'var z = (3 * 5)', ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; - let findController = editor.registerAndInstantiateContribution(TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); findController.start({ forceRevealReplace: false, seedSearchStringFromSelection: false, @@ -322,7 +322,7 @@ suite('FindController', () => { 'HRESULT OnAmbientPropertyChange(DISPID dispid);' ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; - let findController = editor.registerAndInstantiateContribution(TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let startFindAction = new StartFindAction(); startFindAction.run(null, editor); @@ -349,7 +349,7 @@ suite('FindController', () => { 'line3' ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; - let findController = editor.registerAndInstantiateContribution(TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let startFindAction = new StartFindAction(); startFindAction.run(null, editor); @@ -376,7 +376,7 @@ suite('FindController', () => { '([funny]' ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; - let findController = editor.registerAndInstantiateContribution(TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let nextSelectionMatchFindAction = new NextSelectionMatchFindAction(); // toggle regex @@ -403,7 +403,7 @@ suite('FindController', () => { '([funny]' ], { serviceCollection: serviceCollection }, (editor, cursor) => { clipboardState = ''; - let findController = editor.registerAndInstantiateContribution(TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let startFindAction = new StartFindAction(); let nextSelectionMatchFindAction = new NextSelectionMatchFindAction(); @@ -454,7 +454,7 @@ suite('FindController query options persistence', () => { ], { serviceCollection: serviceCollection }, (editor, cursor) => { queryState = { 'editor.isRegex': false, 'editor.matchCase': true, 'editor.wholeWord': false }; // The cursor is at the very top, of the file, at the first ABC - let findController = editor.registerAndInstantiateContribution(TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let findState = findController.getState(); let startFindAction = new StartFindAction(); @@ -481,7 +481,7 @@ suite('FindController query options persistence', () => { ], { serviceCollection: serviceCollection }, (editor, cursor) => { queryState = { 'editor.isRegex': false, 'editor.matchCase': false, 'editor.wholeWord': true }; // The cursor is at the very top, of the file, at the first ABC - let findController = editor.registerAndInstantiateContribution(TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); let findState = findController.getState(); let startFindAction = new StartFindAction(); @@ -506,7 +506,7 @@ suite('FindController query options persistence', () => { ], { serviceCollection: serviceCollection }, (editor, cursor) => { queryState = { 'editor.isRegex': false, 'editor.matchCase': false, 'editor.wholeWord': true }; // The cursor is at the very top, of the file, at the first ABC - let findController = editor.registerAndInstantiateContribution(TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); findController.toggleRegex(); assert.equal(queryState['editor.isRegex'], true); @@ -522,7 +522,7 @@ suite('FindController query options persistence', () => { ], { serviceCollection: serviceCollection, find: { autoFindInSelection: true, globalFindClipboard: false } }, (editor, cursor) => { // clipboardState = ''; editor.setSelection(new Range(1, 1, 2, 1)); - let findController = editor.registerAndInstantiateContribution(TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); findController.start({ forceRevealReplace: false, @@ -545,7 +545,7 @@ suite('FindController query options persistence', () => { ], { serviceCollection: serviceCollection, find: { autoFindInSelection: true, globalFindClipboard: false } }, (editor, cursor) => { // clipboardState = ''; editor.setSelection(new Range(1, 2, 1, 2)); - let findController = editor.registerAndInstantiateContribution(TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); findController.start({ forceRevealReplace: false, @@ -568,7 +568,7 @@ suite('FindController query options persistence', () => { ], { serviceCollection: serviceCollection, find: { autoFindInSelection: true, globalFindClipboard: false } }, (editor, cursor) => { // clipboardState = ''; editor.setSelection(new Range(1, 2, 1, 3)); - let findController = editor.registerAndInstantiateContribution(TestFindController); + let findController = editor.registerAndInstantiateContribution(TestFindController.ID, TestFindController); findController.start({ forceRevealReplace: false, diff --git a/src/vs/editor/contrib/find/test/replacePattern.test.ts b/src/vs/editor/contrib/find/test/replacePattern.test.ts index 23e4ab13b3..d7c8c391fd 100644 --- a/src/vs/editor/contrib/find/test/replacePattern.test.ts +++ b/src/vs/editor/contrib/find/test/replacePattern.test.ts @@ -183,6 +183,10 @@ suite('Replace Pattern test', () => { assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar-newabc'), 'Newfoo-Newbar-Newabc'); actual = ['Foo-Bar-abc']; assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar'), 'Newfoo-newbar'); + actual = ['foo-Bar']; + assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar'), 'newfoo-Newbar'); + actual = ['foo-BAR']; + assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo-newbar'), 'newfoo-NEWBAR'); actual = ['Foo_Bar']; assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar'), 'Newfoo_Newbar'); @@ -192,6 +196,10 @@ suite('Replace Pattern test', () => { assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar'), 'Newfoo_newbar'); actual = ['Foo_Bar-abc']; assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar-abc'), 'Newfoo_newbar-abc'); + actual = ['foo_Bar']; + assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar'), 'newfoo_Newbar'); + actual = ['Foo_BAR']; + assert.equal(buildReplaceStringWithCasePreserved(actual, 'newfoo_newbar'), 'Newfoo_NEWBAR'); }); test('preserve case', () => { @@ -227,6 +235,14 @@ suite('Replace Pattern test', () => { actual = replacePattern.buildReplaceString(['Foo-Bar-abc'], true); assert.equal(actual, 'Newfoo-newbar'); + replacePattern = parseReplaceString('newfoo-newbar'); + actual = replacePattern.buildReplaceString(['foo-Bar'], true); + assert.equal(actual, 'newfoo-Newbar'); + + replacePattern = parseReplaceString('newfoo-newbar'); + actual = replacePattern.buildReplaceString(['foo-BAR'], true); + assert.equal(actual, 'newfoo-NEWBAR'); + replacePattern = parseReplaceString('newfoo_newbar'); actual = replacePattern.buildReplaceString(['Foo_Bar'], true); assert.equal(actual, 'Newfoo_Newbar'); @@ -242,5 +258,13 @@ suite('Replace Pattern test', () => { replacePattern = parseReplaceString('newfoo_newbar-abc'); actual = replacePattern.buildReplaceString(['Foo_Bar-abc'], true); assert.equal(actual, 'Newfoo_newbar-abc'); + + replacePattern = parseReplaceString('newfoo_newbar'); + actual = replacePattern.buildReplaceString(['foo_Bar'], true); + assert.equal(actual, 'newfoo_Newbar'); + + replacePattern = parseReplaceString('newfoo_newbar'); + actual = replacePattern.buildReplaceString(['foo_BAR'], true); + assert.equal(actual, 'newfoo_NEWBAR'); }); }); diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index 372de6f425..fe119dab62 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -34,7 +34,6 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; const CONTEXT_FOLDING_ENABLED = new RawContextKey('foldingEnabled', false); -export const ID = 'editor.contrib.folding'; export interface RangeProvider { readonly id: string; @@ -50,11 +49,12 @@ interface FoldingStateMemento { export class FoldingController extends Disposable implements IEditorContribution { - static MAX_FOLDING_REGIONS = 5000; + public static ID = 'editor.contrib.folding'; + static readonly MAX_FOLDING_REGIONS = 5000; public static get(editor: ICodeEditor): FoldingController { - return editor.getContribution(ID); + return editor.getContribution(FoldingController.ID); } private readonly editor: ICodeEditor; @@ -134,10 +134,6 @@ export class FoldingController extends Disposable implements IEditorContribution this.onModelChanged(); } - public getId(): string { - return ID; - } - /** * Store view state. */ @@ -667,7 +663,7 @@ class ToggleFoldAction extends FoldingAction { precondition: CONTEXT_FOLDING_ENABLED, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_K), + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_L), weight: KeybindingWeight.EditorContrib } }); @@ -856,7 +852,7 @@ class FoldLevelAction extends FoldingAction { } } -registerEditorContribution(FoldingController); +registerEditorContribution(FoldingController.ID, FoldingController); registerEditorAction(UnfoldAction); registerEditorAction(UnFoldRecursivelyAction); registerEditorAction(FoldAction); diff --git a/src/vs/editor/contrib/folding/foldingDecorations.ts b/src/vs/editor/contrib/folding/foldingDecorations.ts index 8257c1c888..406aaa5d08 100644 --- a/src/vs/editor/contrib/folding/foldingDecorations.ts +++ b/src/vs/editor/contrib/folding/foldingDecorations.ts @@ -10,18 +10,18 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; export class FoldingDecorationProvider implements IDecorationProvider { - private static COLLAPSED_VISUAL_DECORATION = ModelDecorationOptions.register({ + private static readonly COLLAPSED_VISUAL_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, afterContentClassName: 'inline-folded', linesDecorationsClassName: 'codicon codicon-chevron-right' }); - private static EXPANDED_AUTO_HIDE_VISUAL_DECORATION = ModelDecorationOptions.register({ + private static readonly EXPANDED_AUTO_HIDE_VISUAL_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, linesDecorationsClassName: 'codicon codicon-chevron-down' }); - private static EXPANDED_VISUAL_DECORATION = ModelDecorationOptions.register({ + private static readonly EXPANDED_VISUAL_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, linesDecorationsClassName: 'codicon codicon-chevron-down alwaysShowFoldIcons' }); diff --git a/src/vs/editor/contrib/format/formatActions.ts b/src/vs/editor/contrib/format/formatActions.ts index 85ec4760d7..89a55d3741 100644 --- a/src/vs/editor/contrib/format/formatActions.ts +++ b/src/vs/editor/contrib/format/formatActions.ts @@ -28,7 +28,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; class FormatOnType implements editorCommon.IEditorContribution { - private static readonly ID = 'editor.contrib.autoFormat'; + public static readonly ID = 'editor.contrib.autoFormat'; private readonly _editor: ICodeEditor; private readonly _callOnDispose = new DisposableStore(); @@ -45,10 +45,6 @@ class FormatOnType implements editorCommon.IEditorContribution { this._callOnDispose.add(OnTypeFormattingEditProviderRegistry.onDidChange(this._update, this)); } - getId(): string { - return FormatOnType.ID; - } - dispose(): void { this._callOnDispose.dispose(); this._callOnModel.dispose(); @@ -155,7 +151,7 @@ class FormatOnType implements editorCommon.IEditorContribution { class FormatOnPaste implements editorCommon.IEditorContribution { - private static readonly ID = 'editor.contrib.formatOnPaste'; + public static readonly ID = 'editor.contrib.formatOnPaste'; private readonly _callOnDispose = new DisposableStore(); private readonly _callOnModel = new DisposableStore(); @@ -170,10 +166,6 @@ class FormatOnPaste implements editorCommon.IEditorContribution { this._callOnDispose.add(DocumentRangeFormattingEditProviderRegistry.onDidChange(this._update, this)); } - getId(): string { - return FormatOnPaste.ID; - } - dispose(): void { this._callOnDispose.dispose(); this._callOnModel.dispose(); @@ -278,8 +270,8 @@ class FormatSelectionAction extends EditorAction { } } -registerEditorContribution(FormatOnType); -registerEditorContribution(FormatOnPaste); +registerEditorContribution(FormatOnType.ID, FormatOnType); +registerEditorContribution(FormatOnPaste.ID, FormatOnPaste); registerEditorAction(FormatDocumentAction); registerEditorAction(FormatSelectionAction); diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts b/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts index 4e10a88f80..653f47f74b 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts +++ b/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts @@ -31,6 +31,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { EditorStateCancellationTokenSource, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState'; import { ISymbolNavigationService } from 'vs/editor/contrib/goToDefinition/goToDefinitionResultsNavigation'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { isEqual } from 'vs/base/common/resources'; export class DefinitionActionConfig { @@ -84,7 +85,7 @@ export class DefinitionAction extends EditorAction { } const newLen = result.push(reference); if (this._configuration.filterCurrent - && reference.uri.toString() === model.uri.toString() + && isEqual(reference.uri, model.uri) && Range.containsPosition(reference.range, pos) && idxOfCurrent === -1 ) { @@ -162,7 +163,7 @@ export class DefinitionAction extends EditorAction { } } - private _openReference(editor: ICodeEditor, editorService: ICodeEditorService, reference: Location | LocationLink, sideBySide: boolean): Promise { + private _openReference(editor: ICodeEditor, editorService: ICodeEditorService, reference: Location | LocationLink, sideBySide: boolean): Promise { // range is the target-selection-range when we have one // and the the fallback is the 'full' range let range: IRange | undefined = undefined; diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts b/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts index 3a8499aea9..81f95ab59f 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts +++ b/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts @@ -29,8 +29,8 @@ import { withNullAsUndefined } from 'vs/base/common/types'; class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorContribution { - private static readonly ID = 'editor.contrib.gotodefinitionwithmouse'; - static MAX_SOURCE_PREVIEW_LINES = 8; + public static readonly ID = 'editor.contrib.gotodefinitionwithmouse'; + static readonly MAX_SOURCE_PREVIEW_LINES = 8; private readonly editor: ICodeEditor; private readonly toUnhook = new DisposableStore(); @@ -220,7 +220,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC brackets.push(currentBracket); } else { const lastBracket = brackets[brackets.length - 1]; - if (lastBracket.open === currentBracket.open && lastBracket.isOpen && !currentBracket.isOpen) { + if (lastBracket.open[0] === currentBracket.open[0] && lastBracket.isOpen && !currentBracket.isOpen) { brackets.pop(); } else { brackets.push(currentBracket); @@ -295,16 +295,12 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC return this.editor.invokeWithinContext(accessor => action.run(accessor, this.editor)); } - public getId(): string { - return GotoDefinitionWithMouseEditorContribution.ID; - } - public dispose(): void { this.toUnhook.dispose(); } } -registerEditorContribution(GotoDefinitionWithMouseEditorContribution); +registerEditorContribution(GotoDefinitionWithMouseEditorContribution.ID, GotoDefinitionWithMouseEditorContribution); registerThemingParticipant((theme, collector) => { const activeLinkForeground = theme.getColor(editorActiveLinkForeground); diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionResultsNavigation.ts b/src/vs/editor/contrib/goToDefinition/goToDefinitionResultsNavigation.ts index 09e5065aa6..495cf11f11 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionResultsNavigation.ts +++ b/src/vs/editor/contrib/goToDefinition/goToDefinitionResultsNavigation.ts @@ -18,6 +18,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { isEqual } from 'vs/base/common/resources'; export const ctxHasSymbols = new RawContextKey('hasSymbols', false); @@ -92,7 +93,7 @@ class SymbolNavigationService implements ISymbolNavigationService { let seenUri: boolean = false; let seenPosition: boolean = false; for (const reference of refModel.references) { - if (reference.uri.toString() === model.uri.toString()) { + if (isEqual(reference.uri, model.uri)) { seenUri = true; seenPosition = seenPosition || Range.containsPosition(reference.range, position); } else if (seenUri) { diff --git a/src/vs/editor/contrib/gotoError/gotoError.ts b/src/vs/editor/contrib/gotoError/gotoError.ts index 68eb858450..a30ebdc174 100644 --- a/src/vs/editor/contrib/gotoError/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/gotoError.ts @@ -20,12 +20,13 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { MarkerNavigationWidget } from './gotoErrorWidget'; import { compare } from 'vs/base/common/strings'; -import { binarySearch } from 'vs/base/common/arrays'; +import { binarySearch, find } from 'vs/base/common/arrays'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { onUnexpectedError } from 'vs/base/common/errors'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { Action } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { isEqual } from 'vs/base/common/resources'; class MarkerModel { @@ -172,12 +173,7 @@ class MarkerModel { } public findMarkerAtPosition(pos: Position): IMarker | undefined { - for (const marker of this._markers) { - if (Range.containsPosition(marker, pos)) { - return marker; - } - } - return undefined; + return find(this._markers, marker => Range.containsPosition(marker, pos)); } public get total() { @@ -195,7 +191,7 @@ class MarkerModel { export class MarkerController implements editorCommon.IEditorContribution { - private static readonly ID = 'editor.contrib.markerController'; + public static readonly ID = 'editor.contrib.markerController'; public static get(editor: ICodeEditor): MarkerController { return editor.getContribution(MarkerController.ID); @@ -219,10 +215,6 @@ export class MarkerController implements editorCommon.IEditorContribution { this._widgetVisible = CONTEXT_MARKERS_NAVIGATION_VISIBLE.bindTo(this._contextKeyService); } - public getId(): string { - return MarkerController.ID; - } - public dispose(): void { this._cleanUp(); this._disposeOnClose.dispose(); @@ -248,8 +240,8 @@ export class MarkerController implements editorCommon.IEditorContribution { const prevMarkerKeybinding = this._keybindingService.lookupKeybinding(PrevMarkerAction.ID); const nextMarkerKeybinding = this._keybindingService.lookupKeybinding(NextMarkerAction.ID); const actions = [ - new Action(PrevMarkerAction.ID, PrevMarkerAction.LABEL + (prevMarkerKeybinding ? ` (${prevMarkerKeybinding.getLabel()})` : ''), 'show-previous-problem chevron-up', this._model.canNavigate(), async () => { if (this._model) { this._model.move(false, true); } }), - new Action(NextMarkerAction.ID, NextMarkerAction.LABEL + (nextMarkerKeybinding ? ` (${nextMarkerKeybinding.getLabel()})` : ''), 'show-next-problem chevron-down', this._model.canNavigate(), async () => { if (this._model) { this._model.move(true, true); } }) + new Action(PrevMarkerAction.ID, PrevMarkerAction.LABEL + (prevMarkerKeybinding ? ` (${prevMarkerKeybinding.getLabel()})` : ''), 'show-previous-problem codicon-chevron-up', this._model.canNavigate(), async () => { if (this._model) { this._model.move(false, true); } }), + new Action(NextMarkerAction.ID, NextMarkerAction.LABEL + (nextMarkerKeybinding ? ` (${nextMarkerKeybinding.getLabel()})` : ''), 'show-next-problem codicon-chevron-down', this._model.canNavigate(), async () => { if (this._model) { this._model.move(true, true); } }) ]; this._widget = new MarkerNavigationWidget(this._editor, actions, this._themeService); this._widgetVisible.set(true); @@ -310,7 +302,7 @@ export class MarkerController implements editorCommon.IEditorContribution { } private _onMarkerChanged(changedResources: URI[]): void { - let editorModel = this._editor.getModel(); + const editorModel = this._editor.getModel(); if (!editorModel) { return; } @@ -319,7 +311,7 @@ export class MarkerController implements editorCommon.IEditorContribution { return; } - if (!changedResources.some(r => editorModel!.uri.toString() === r.toString())) { + if (!changedResources.some(r => isEqual(editorModel.uri, r))) { return; } this._model.setMarkers(this._getMarkers()); @@ -371,7 +363,7 @@ class MarkerNavigationAction extends EditorAction { return Promise.resolve(undefined); } - let editorModel = editor.getModel(); + const editorModel = editor.getModel(); if (!editorModel) { return Promise.resolve(undefined); } @@ -389,7 +381,7 @@ class MarkerNavigationAction extends EditorAction { } let newMarker = markers[idx]; - if (newMarker.resource.toString() === editorModel!.uri.toString()) { + if (isEqual(newMarker.resource, editorModel.uri)) { // the next `resource` is this resource which // means we cycle within this file model.move(this._isNext, true); @@ -483,7 +475,7 @@ class PrevMarkerInFilesAction extends MarkerNavigationAction { } } -registerEditorContribution(MarkerController); +registerEditorContribution(MarkerController.ID, MarkerController); registerEditorAction(NextMarkerAction); registerEditorAction(PrevMarkerAction); registerEditorAction(NextMarkerInFilesAction); @@ -522,4 +514,4 @@ MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { title: nls.localize({ key: 'miGotoPreviousProblem', comment: ['&& denotes a mnemonic'] }, "Previous &&Problem") }, order: 2 -}); \ No newline at end of file +}); diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index 1d95386839..4049d8a199 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -283,7 +283,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { : nls.localize('change', "{0} of {1} problem", markerIdx, markerCount); this.setTitle(basename(model.uri), detail); } - this._icon.className = SeverityIcon.className(MarkerSeverity.toSeverity(this._severity)); + this._icon.className = `codicon ${SeverityIcon.className(MarkerSeverity.toSeverity(this._severity))}`; this.editor.revealPositionInCenter(position, ScrollType.Smooth); } diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index f0d49d3764..c970018737 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, DisposableStore } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore, MutableDisposable } 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'; @@ -29,26 +29,26 @@ import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibi export class ModesHoverController implements IEditorContribution { - private static readonly ID = 'editor.contrib.hover'; + public static readonly ID = 'editor.contrib.hover'; private readonly _toUnhook = new DisposableStore(); private readonly _didChangeConfigurationHandler: IDisposable; - private _contentWidget: ModesContentHoverWidget | null; - private _glyphWidget: ModesGlyphHoverWidget | null; + private readonly _contentWidget = new MutableDisposable(); + private readonly _glyphWidget = new MutableDisposable(); get contentWidget(): ModesContentHoverWidget { - if (!this._contentWidget) { + if (!this._contentWidget.value) { this._createHoverWidgets(); } - return this._contentWidget!; + return this._contentWidget.value!; } get glyphWidget(): ModesGlyphHoverWidget { - if (!this._glyphWidget) { + if (!this._glyphWidget.value) { this._createHoverWidgets(); } - return this._glyphWidget!; + return this._glyphWidget.value!; } private _isMouseDown: boolean; @@ -69,8 +69,6 @@ export class ModesHoverController implements IEditorContribution { ) { this._isMouseDown = false; this._hoverClicked = false; - this._contentWidget = null; - this._glyphWidget = null; this._hookEvents(); @@ -197,38 +195,29 @@ export class ModesHoverController implements IEditorContribution { } private _hideWidgets(): void { - if (!this._glyphWidget || !this._contentWidget || (this._isMouseDown && this._hoverClicked && this._contentWidget.isColorPickerVisible())) { + if (!this._glyphWidget.value || !this._contentWidget.value || (this._isMouseDown && this._hoverClicked && this._contentWidget.value.isColorPickerVisible())) { return; } - this._glyphWidget!.hide(); - this._contentWidget.hide(); + this._glyphWidget.value.hide(); + this._contentWidget.value.hide(); } private _createHoverWidgets() { - this._contentWidget = new ModesContentHoverWidget(this._editor, this._markerDecorationsService, this._themeService, this._keybindingService, this._modeService, this._openerService); - this._glyphWidget = new ModesGlyphHoverWidget(this._editor, this._modeService, this._openerService); + this._contentWidget.value = new ModesContentHoverWidget(this._editor, this._markerDecorationsService, this._themeService, this._keybindingService, this._modeService, this._openerService); + this._glyphWidget.value = new ModesGlyphHoverWidget(this._editor, this._modeService, this._openerService); } public showContentHover(range: Range, mode: HoverStartMode, focus: boolean): void { this.contentWidget.startShowingAt(range, mode, focus); } - public getId(): string { - return ModesHoverController.ID; - } - public dispose(): void { this._unhookEvents(); this._toUnhook.dispose(); this._didChangeConfigurationHandler.dispose(); - - if (this._glyphWidget) { - this._glyphWidget.dispose(); - } - if (this._contentWidget) { - this._contentWidget.dispose(); - } + this._glyphWidget.dispose(); + this._contentWidget.dispose(); } } @@ -269,7 +258,7 @@ class ShowHoverAction extends EditorAction { } } -registerEditorContribution(ModesHoverController); +registerEditorContribution(ModesHoverController.ID, ModesHoverController); registerEditorAction(ShowHoverAction); // theming diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 3307007ef6..c44fe4dd2b 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -38,6 +38,7 @@ import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { Constants } from 'vs/base/common/uint'; const $ = dom.$; @@ -333,7 +334,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this._colorPicker = null; // update column from which to show - let renderColumn = Number.MAX_VALUE; + let renderColumn = Constants.MAX_SAFE_SMALL_INTEGER; let highlightRange: Range | null = messages[0].range ? Range.lift(messages[0].range) : null; let fragment = document.createDocumentFragment(); let isEmptyHoverContent = true; @@ -505,7 +506,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { e.stopPropagation(); e.preventDefault(); if (this._openerService) { - this._openerService.open(resource.with({ fragment: `${startLineNumber},${startColumn}` })).catch(onUnexpectedError); + this._openerService.open(resource.with({ fragment: `${startLineNumber},${startColumn}` }), { fromUserGesture: true }).catch(onUnexpectedError); } }; const messageElement = dom.append(relatedInfoContainer, $('span')); diff --git a/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts b/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts index 1f2520da13..20213213b1 100644 --- a/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts +++ b/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts @@ -24,7 +24,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis class InPlaceReplaceController implements IEditorContribution { - private static readonly ID = 'editor.contrib.inPlaceReplaceController'; + public static readonly ID = 'editor.contrib.inPlaceReplaceController'; static get(editor: ICodeEditor): InPlaceReplaceController { return editor.getContribution(InPlaceReplaceController.ID); @@ -51,10 +51,6 @@ class InPlaceReplaceController implements IEditorContribution { public dispose(): void { } - public getId(): string { - return InPlaceReplaceController.ID; - } - public run(source: string, up: boolean): Promise | undefined { // cancel any pending request @@ -183,7 +179,7 @@ class InPlaceReplaceDown extends EditorAction { } } -registerEditorContribution(InPlaceReplaceController); +registerEditorContribution(InPlaceReplaceController.ID, InPlaceReplaceController); registerEditorAction(InPlaceReplaceUp); registerEditorAction(InPlaceReplaceDown); diff --git a/src/vs/editor/contrib/indentation/indentation.ts b/src/vs/editor/contrib/indentation/indentation.ts index 8d37c33c1c..97f773336f 100644 --- a/src/vs/editor/contrib/indentation/indentation.ts +++ b/src/vs/editor/contrib/indentation/indentation.ts @@ -422,7 +422,7 @@ export class AutoIndentOnPasteCommand implements ICommand { } export class AutoIndentOnPaste implements IEditorContribution { - private static readonly ID = 'editor.contrib.autoIndentOnPaste'; + public static readonly ID = 'editor.contrib.autoIndentOnPaste'; private readonly editor: ICodeEditor; private readonly callOnDispose = new DisposableStore(); @@ -604,10 +604,6 @@ export class AutoIndentOnPaste implements IEditorContribution { return false; } - public getId(): string { - return AutoIndentOnPaste.ID; - } - public dispose(): void { this.callOnDispose.dispose(); this.callOnModel.dispose(); @@ -681,7 +677,7 @@ export class IndentationToTabsCommand implements ICommand { } } -registerEditorContribution(AutoIndentOnPaste); +registerEditorContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste); registerEditorAction(IndentationToSpacesAction); registerEditorAction(IndentationToTabsAction); registerEditorAction(IndentUsingTabs); diff --git a/src/vs/editor/contrib/linesOperations/linesOperations.ts b/src/vs/editor/contrib/linesOperations/linesOperations.ts index f6819b067e..6416da84d4 100644 --- a/src/vs/editor/contrib/linesOperations/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/linesOperations.ts @@ -8,7 +8,7 @@ import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; import { ICodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, IActionOptions, ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; -import { ReplaceCommand, ReplaceCommandThatPreservesSelection } from 'vs/editor/common/commands/replaceCommand'; +import { ReplaceCommand, ReplaceCommandThatPreservesSelection, ReplaceCommandThatSelectsText } from 'vs/editor/common/commands/replaceCommand'; import { TrimTrailingWhitespaceCommand } from 'vs/editor/common/commands/trimTrailingWhitespaceCommand'; import { TypeOperations } from 'vs/editor/common/controller/cursorTypeOperations'; import { EditOperation } from 'vs/editor/common/core/editOperation'; @@ -97,6 +97,47 @@ class CopyLinesDownAction extends AbstractCopyLinesAction { } } +export class DuplicateSelectionAction extends EditorAction { + + constructor() { + super({ + id: 'editor.action.duplicateSelection', + label: nls.localize('duplicateSelection', "Duplicate Selection"), + alias: 'Duplicate Selection', + precondition: EditorContextKeys.writable, + menubarOpts: { + menuId: MenuId.MenubarSelectionMenu, + group: '2_line', + title: nls.localize({ key: 'miDuplicateSelection', comment: ['&& denotes a mnemonic'] }, "&&Duplicate Selection"), + order: 5 + } + }); + } + + public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + if (!editor.hasModel()) { + return; + } + + const commands: ICommand[] = []; + const selections = editor.getSelections(); + const model = editor.getModel(); + + for (const selection of selections) { + if (selection.isEmpty()) { + commands.push(new CopyLinesCommand(selection, true)); + } else { + const insertSelection = new Selection(selection.endLineNumber, selection.endColumn, selection.endLineNumber, selection.endColumn); + commands.push(new ReplaceCommandThatSelectsText(insertSelection, model.getValueInRange(selection))); + } + } + + editor.pushUndoStop(); + editor.executeCommands(this.id, commands); + editor.pushUndoStop(); + } +} + // move lines abstract class AbstractMoveLinesAction extends EditorAction { @@ -989,6 +1030,7 @@ export class TitleCaseAction extends AbstractCaseAction { registerEditorAction(CopyLinesUpAction); registerEditorAction(CopyLinesDownAction); +registerEditorAction(DuplicateSelectionAction); registerEditorAction(MoveLinesUpAction); registerEditorAction(MoveLinesDownAction); registerEditorAction(SortLinesAscendingAction); diff --git a/src/vs/editor/contrib/linesOperations/test/copyLinesCommand.test.ts b/src/vs/editor/contrib/linesOperations/test/copyLinesCommand.test.ts index b8c8293b9e..41b7b0eab9 100644 --- a/src/vs/editor/contrib/linesOperations/test/copyLinesCommand.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/copyLinesCommand.test.ts @@ -3,9 +3,12 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as assert from 'assert'; import { Selection } from 'vs/editor/common/core/selection'; import { CopyLinesCommand } from 'vs/editor/contrib/linesOperations/copyLinesCommand'; import { testCommand } from 'vs/editor/test/browser/testCommand'; +import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; +import { DuplicateSelectionAction } from 'vs/editor/contrib/linesOperations/linesOperations'; function testCopyLinesDownCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void { testCommand(lines, null, selection, (sel) => new CopyLinesCommand(sel, true), expectedLines, expectedSelection); @@ -195,3 +198,61 @@ suite('Editor Contrib - Copy Lines Command', () => { ); }); }); + +suite('Editor Contrib - Duplicate Selection', () => { + + const duplicateSelectionAction = new DuplicateSelectionAction(); + + function testDuplicateSelectionAction(lines: string[], selections: Selection[], expectedLines: string[], expectedSelections: Selection[]): void { + withTestCodeEditor(lines.join('\n'), {}, (editor, cursor) => { + editor.setSelections(selections); + duplicateSelectionAction.run(null!, editor, {}); + assert.deepEqual(editor.getValue(), expectedLines.join('\n')); + assert.deepEqual(editor.getSelections()!.map(s => s.toString()), expectedSelections.map(s => s.toString())); + }); + } + + test('empty selection', function () { + testDuplicateSelectionAction( + [ + 'first', + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + [new Selection(2, 2, 2, 2), new Selection(3, 2, 3, 2)], + [ + 'first', + 'second line', + 'second line', + 'third line', + 'third line', + 'fourth line', + 'fifth' + ], + [new Selection(3, 2, 3, 2), new Selection(5, 2, 5, 2)] + ); + }); + + test('with selection', function () { + testDuplicateSelectionAction( + [ + 'first', + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + [new Selection(2, 1, 2, 4), new Selection(3, 1, 3, 4)], + [ + 'first', + 'secsecond line', + 'thithird line', + 'fourth line', + 'fifth' + ], + [new Selection(2, 4, 2, 7), new Selection(3, 4, 3, 7)] + ); + }); +}); diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index ad2febc268..d6b1646443 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -44,8 +44,7 @@ function getHoverMessage(link: Link, useMetaKey: boolean): MarkdownString { : nls.localize('links.navigate.kb.alt', "alt + click"); if (link.url) { - const hoverMessage = new MarkdownString().appendMarkdown(`[${label}](${link.url.toString()}) (${kb})`); - hoverMessage.isTrusted = true; + const hoverMessage = new MarkdownString('', true).appendMarkdown(`[${label}](${link.url.toString()}) (${kb})`); return hoverMessage; } else { return new MarkdownString().appendText(`${label} (${kb})`); @@ -100,13 +99,13 @@ class LinkOccurrence { class LinkDetector implements editorCommon.IEditorContribution { - private static readonly ID: string = 'editor.linkDetector'; + public static readonly ID: string = 'editor.linkDetector'; public static get(editor: ICodeEditor): LinkDetector { return editor.getContribution(LinkDetector.ID); } - static RECOMPUTE_TIME = 1000; // ms + static readonly RECOMPUTE_TIME = 1000; // ms private readonly editor: ICodeEditor; private enabled: boolean; @@ -171,10 +170,6 @@ class LinkDetector implements editorCommon.IEditorContribution { this.beginCompute(); } - public getId(): string { - return LinkDetector.ID; - } - private onModelChanged(): void { this.currentOccurrences = {}; this.activeLinkDecorationId = null; @@ -284,10 +279,10 @@ class LinkDetector implements editorCommon.IEditorContribution { if (!occurrence) { return; } - this.openLinkOccurrence(occurrence, mouseEvent.hasSideBySideModifier); + this.openLinkOccurrence(occurrence, mouseEvent.hasSideBySideModifier, true /* from user gesture */); } - public openLinkOccurrence(occurrence: LinkOccurrence, openToSide: boolean): void { + public openLinkOccurrence(occurrence: LinkOccurrence, openToSide: boolean, fromUserGesture = false): void { if (!this.openerService) { return; @@ -297,7 +292,7 @@ class LinkDetector implements editorCommon.IEditorContribution { link.resolve(CancellationToken.None).then(uri => { // open the uri - return this.openerService.open(uri, { openToSide }); + return this.openerService.open(uri, { openToSide, fromUserGesture }); }, err => { const messageOrError = @@ -391,7 +386,7 @@ class OpenLinkAction extends EditorAction { } } -registerEditorContribution(LinkDetector); +registerEditorContribution(LinkDetector.ID, LinkDetector); registerEditorAction(OpenLinkAction); registerThemingParticipant((theme, collector) => { diff --git a/src/vs/editor/contrib/markdown/markdownRenderer.ts b/src/vs/editor/contrib/markdown/markdownRenderer.ts index 23701c2369..d11fa34ca4 100644 --- a/src/vs/editor/contrib/markdown/markdownRenderer.ts +++ b/src/vs/editor/contrib/markdown/markdownRenderer.ts @@ -71,7 +71,7 @@ export class MarkdownRenderer extends Disposable { // ignore } if (uri && this._openerService) { - this._openerService.open(uri).catch(onUnexpectedError); + this._openerService.open(uri, { fromUserGesture: true }).catch(onUnexpectedError); } }, disposeables diff --git a/src/vs/editor/contrib/message/messageController.ts b/src/vs/editor/contrib/message/messageController.ts index 65a6f5bef2..5fc613e290 100644 --- a/src/vs/editor/contrib/message/messageController.ts +++ b/src/vs/editor/contrib/message/messageController.ts @@ -21,20 +21,16 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis export class MessageController extends Disposable implements editorCommon.IEditorContribution { - private static readonly _id = 'editor.contrib.messageController'; + public static readonly ID = 'editor.contrib.messageController'; - static MESSAGE_VISIBLE = new RawContextKey('messageVisible', false); + static readonly MESSAGE_VISIBLE = new RawContextKey('messageVisible', false); static get(editor: ICodeEditor): MessageController { - return editor.getContribution(MessageController._id); + return editor.getContribution(MessageController.ID); } private readonly closeTimeout = 3000; // close after 3s - getId(): string { - return MessageController._id; - } - private readonly _editor: ICodeEditor; private readonly _visible: IContextKey; private readonly _messageWidget = this._register(new MutableDisposable()); @@ -184,7 +180,7 @@ class MessageWidget implements IContentWidget { } } -registerEditorContribution(MessageController); +registerEditorContribution(MessageController.ID, MessageController); registerThemingParticipant((theme, collector) => { const border = theme.getColor(inputValidationInfoBorder); diff --git a/src/vs/editor/contrib/multicursor/multicursor.ts b/src/vs/editor/contrib/multicursor/multicursor.ts index 87f807d313..2b1162aa86 100644 --- a/src/vs/editor/contrib/multicursor/multicursor.ts +++ b/src/vs/editor/contrib/multicursor/multicursor.ts @@ -14,7 +14,7 @@ import { CursorChangeReason, ICursorSelectionChangedEvent } from 'vs/editor/comm import { CursorMoveCommands } from 'vs/editor/common/controller/cursorMoveCommands'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import { Constants } from 'vs/editor/common/core/uint'; +import { Constants } from 'vs/base/common/uint'; import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { FindMatch, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; @@ -423,7 +423,7 @@ export class MultiCursorSession { export class MultiCursorSelectionController extends Disposable implements IEditorContribution { - private static readonly ID = 'editor.contrib.multiCursorController'; + public static readonly ID = 'editor.contrib.multiCursorController'; private readonly _editor: ICodeEditor; private _ignoreSelectionChange: boolean; @@ -446,10 +446,6 @@ export class MultiCursorSelectionController extends Disposable implements IEdito super.dispose(); } - public getId(): string { - return MultiCursorSelectionController.ID; - } - private _beginSessionIfNeeded(findController: CommonFindController): void { if (!this._session) { // Create a new session @@ -798,7 +794,7 @@ class SelectionHighlighterState { } export class SelectionHighlighter extends Disposable implements IEditorContribution { - private static readonly ID = 'editor.contrib.selectionHighlighter'; + public static readonly ID = 'editor.contrib.selectionHighlighter'; private readonly editor: ICodeEditor; private _isEnabled: boolean; @@ -848,10 +844,6 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut })); } - public getId(): string { - return SelectionHighlighter.ID; - } - private _update(): void { this._setState(SelectionHighlighter._createState(this._isEnabled, this.editor)); } @@ -1041,8 +1033,8 @@ function getValueInRange(model: ITextModel, range: Range, toLowerCase: boolean): return (toLowerCase ? text.toLowerCase() : text); } -registerEditorContribution(MultiCursorSelectionController); -registerEditorContribution(SelectionHighlighter); +registerEditorContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); +registerEditorContribution(SelectionHighlighter.ID, SelectionHighlighter); registerEditorAction(InsertCursorAbove); registerEditorAction(InsertCursorBelow); diff --git a/src/vs/editor/contrib/multicursor/test/multicursor.test.ts b/src/vs/editor/contrib/multicursor/test/multicursor.test.ts index 1bd13e8cc0..84aea22a8e 100644 --- a/src/vs/editor/contrib/multicursor/test/multicursor.test.ts +++ b/src/vs/editor/contrib/multicursor/test/multicursor.test.ts @@ -79,8 +79,8 @@ suite('Multicursor selection', () => { 'var z = (3 * 5)', ], { serviceCollection: serviceCollection }, (editor, cursor) => { - let findController = editor.registerAndInstantiateContribution(CommonFindController); - let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController); + let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); + let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); let selectHighlightsAction = new SelectHighlightsAction(); editor.setSelection(new Selection(2, 9, 2, 16)); @@ -109,8 +109,8 @@ suite('Multicursor selection', () => { 'nothing' ], { serviceCollection: serviceCollection }, (editor, cursor) => { - let findController = editor.registerAndInstantiateContribution(CommonFindController); - let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController); + let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); + let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); let selectHighlightsAction = new SelectHighlightsAction(); editor.setSelection(new Selection(1, 1, 1, 1)); @@ -143,8 +143,8 @@ suite('Multicursor selection', () => { 'rty' ], { serviceCollection: serviceCollection }, (editor, cursor) => { - let findController = editor.registerAndInstantiateContribution(CommonFindController); - let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController); + let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); + let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); let addSelectionToNextFindMatch = new AddSelectionToNextFindMatchAction(); editor.setSelection(new Selection(2, 1, 3, 4)); @@ -171,8 +171,8 @@ suite('Multicursor selection', () => { 'abcabc', ], { serviceCollection: serviceCollection }, (editor, cursor) => { - let findController = editor.registerAndInstantiateContribution(CommonFindController); - let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController); + let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); + let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); let addSelectionToNextFindMatch = new AddSelectionToNextFindMatchAction(); editor.setSelection(new Selection(1, 1, 1, 4)); @@ -228,8 +228,8 @@ suite('Multicursor selection', () => { editor.getModel()!.setEOL(EndOfLineSequence.CRLF); - let findController = editor.registerAndInstantiateContribution(CommonFindController); - let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController); + let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); + let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); let addSelectionToNextFindMatch = new AddSelectionToNextFindMatchAction(); editor.setSelection(new Selection(2, 1, 3, 4)); @@ -251,8 +251,8 @@ suite('Multicursor selection', () => { function testMulticursor(text: string[], callback: (editor: TestCodeEditor, findController: CommonFindController) => void): void { withTestCodeEditor(text, { serviceCollection: serviceCollection }, (editor, cursor) => { - let findController = editor.registerAndInstantiateContribution(CommonFindController); - let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController); + let findController = editor.registerAndInstantiateContribution(CommonFindController.ID, CommonFindController); + let multiCursorSelectController = editor.registerAndInstantiateContribution(MultiCursorSelectionController.ID, MultiCursorSelectionController); callback(editor, findController); diff --git a/src/vs/editor/contrib/parameterHints/parameterHints.ts b/src/vs/editor/contrib/parameterHints/parameterHints.ts index 9c8a2ca29b..3923373059 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHints.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHints.ts @@ -20,7 +20,7 @@ import { TriggerContext } from 'vs/editor/contrib/parameterHints/parameterHintsM class ParameterHintsController extends Disposable implements IEditorContribution { - private static readonly ID = 'editor.controller.parameterHints'; + public static readonly ID = 'editor.controller.parameterHints'; public static get(editor: ICodeEditor): ParameterHintsController { return editor.getContribution(ParameterHintsController.ID); @@ -35,10 +35,6 @@ class ParameterHintsController extends Disposable implements IEditorContribution this.widget = this._register(instantiationService.createInstance(ParameterHintsWidget, this.editor)); } - getId(): string { - return ParameterHintsController.ID; - } - cancel(): void { this.widget.cancel(); } @@ -82,7 +78,7 @@ export class TriggerParameterHintsAction extends EditorAction { } } -registerEditorContribution(ParameterHintsController); +registerEditorContribution(ParameterHintsController.ID, ParameterHintsController); registerEditorAction(TriggerParameterHintsAction); const weight = KeybindingWeight.EditorContrib + 75; diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsModel.ts b/src/vs/editor/contrib/parameterHints/parameterHintsModel.ts index dc05dee363..d6b7720afe 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsModel.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsModel.ts @@ -55,6 +55,7 @@ export class ParameterHintsModel extends Disposable { private readonly editor: ICodeEditor; private triggerOnType = false; private _state: ParameterHintState.State = ParameterHintState.Default; + private _pendingTriggers: TriggerContext[] = []; private readonly _lastSignatureHelpResult = this._register(new MutableDisposable()); private triggerChars = new CharacterSet(); private retriggerChars = new CharacterSet(); @@ -109,13 +110,12 @@ export class ParameterHintsModel extends Disposable { } const triggerId = ++this.triggerId; - this.throttledDelayer.trigger( - () => this.doTrigger({ - triggerKind: context.triggerKind, - triggerCharacter: context.triggerCharacter, - isRetrigger: this.state.type === ParameterHintState.Type.Active || this.state.type === ParameterHintState.Type.Pending, - activeSignatureHelp: this.state.type === ParameterHintState.Type.Active ? this.state.hints : undefined - }, triggerId), delay).then(undefined, onUnexpectedError); + + this._pendingTriggers.push(context); + this.throttledDelayer.trigger(() => { + return this.doTrigger(triggerId); + }, delay) + .catch(onUnexpectedError); } public next(): void { @@ -165,11 +165,28 @@ export class ParameterHintsModel extends Disposable { this._onChangedHints.fire(this.state.hints); } - private doTrigger(triggerContext: modes.SignatureHelpContext, triggerId: number): Promise { + private async doTrigger(triggerId: number): Promise { + const isRetrigger = this.state.type === ParameterHintState.Type.Active || this.state.type === ParameterHintState.Type.Pending; + const activeSignatureHelp = this.state.type === ParameterHintState.Type.Active ? this.state.hints : undefined; + this.cancel(true); + if (this._pendingTriggers.length === 0) { + return false; + } + + const context: TriggerContext = this._pendingTriggers.reduce(mergeTriggerContexts); + this._pendingTriggers = []; + + const triggerContext = { + triggerKind: context.triggerKind, + triggerCharacter: context.triggerCharacter, + isRetrigger: isRetrigger, + activeSignatureHelp: activeSignatureHelp + }; + if (!this.editor.hasModel()) { - return Promise.resolve(false); + return false; } const model = this.editor.getModel(); @@ -178,19 +195,18 @@ export class ParameterHintsModel extends Disposable { this.state = new ParameterHintState.Pending(createCancelablePromise(token => provideSignatureHelp(model, position, triggerContext, token))); - return this.state.request.then(result => { + try { + const result = await this.state.request; + // Check that we are still resolving the correct signature help if (triggerId !== this.triggerId) { - if (result) { - result.dispose(); - } + result?.dispose(); + return false; } if (!result || !result.value.signatures || result.value.signatures.length === 0) { - if (result) { - result.dispose(); - } + result?.dispose(); this._lastSignatureHelpResult.clear(); this.cancel(); return false; @@ -200,13 +216,13 @@ export class ParameterHintsModel extends Disposable { this._onChangedHints.fire(this.state.hints); return true; } - }).catch(error => { + } catch (error) { if (triggerId === this.triggerId) { this.state = ParameterHintState.Default; } onUnexpectedError(error); return false; - }); + } } private get isTriggered(): boolean { @@ -284,3 +300,19 @@ export class ParameterHintsModel extends Disposable { super.dispose(); } } + +function mergeTriggerContexts(previous: TriggerContext, current: TriggerContext) { + switch (current.triggerKind) { + case modes.SignatureHelpTriggerKind.Invoke: + // Invoke overrides previous triggers. + return current; + + case modes.SignatureHelpTriggerKind.ContentChange: + // Ignore content changes triggers + return previous; + + case modes.SignatureHelpTriggerKind.TriggerCharacter: + default: + return current; + } +} diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts index 15c44fad54..5b7bdee9d5 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts @@ -34,13 +34,17 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, private readonly model = this._register(new MutableDisposable()); private readonly keyVisible: IContextKey; private readonly keyMultipleSignatures: IContextKey; - private element: HTMLElement; - private signature: HTMLElement; - private docs: HTMLElement; - private overloads: HTMLElement; - private visible: boolean; - private announcedLabel: string | null; - private scrollbar: DomScrollableElement; + + private domNodes?: { + readonly element: HTMLElement; + readonly signature: HTMLElement; + readonly docs: HTMLElement; + readonly overloads: HTMLElement; + readonly scrollbar: DomScrollableElement; + }; + + private visible: boolean = false; + private announcedLabel: string | null = null; // Editor.IContentWidget.allowEditorOverflow allowEditorOverflow = true; @@ -56,7 +60,6 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, this.model.value = new ParameterHintsModel(editor); this.keyVisible = Context.Visible.bindTo(contextKeyService); this.keyMultipleSignatures = Context.MultipleSignatures.bindTo(contextKeyService); - this.visible = false; this._register(this.model.value.onChangedHints(newParameterHints => { if (newParameterHints) { @@ -69,8 +72,8 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, } private createParamaterHintDOMNodes() { - this.element = $('.editor-widget.parameter-hints-widget'); - const wrapper = dom.append(this.element, $('.wrapper')); + const element = $('.editor-widget.parameter-hints-widget'); + const wrapper = dom.append(element, $('.wrapper')); wrapper.tabIndex = -1; const buttons = dom.append(wrapper, $('.buttons')); @@ -83,21 +86,29 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, const onNextClick = stop(domEvent(next, 'click')); this._register(onNextClick(this.next, this)); - this.overloads = dom.append(wrapper, $('.overloads')); + const overloads = dom.append(wrapper, $('.overloads')); const body = $('.body'); - this.scrollbar = new DomScrollableElement(body, {}); - this._register(this.scrollbar); - wrapper.appendChild(this.scrollbar.getDomNode()); + const scrollbar = new DomScrollableElement(body, {}); + this._register(scrollbar); + wrapper.appendChild(scrollbar.getDomNode()); - this.signature = dom.append(body, $('.signature')); + const signature = dom.append(body, $('.signature')); + const docs = dom.append(body, $('.docs')); - this.docs = dom.append(body, $('.docs')); + element.style.userSelect = 'text'; + + this.domNodes = { + element, + signature, + overloads, + docs, + scrollbar, + }; this.editor.addContentWidget(this); this.hide(); - this.element.style.userSelect = 'text'; this._register(this.editor.onDidChangeCursorSelection(e => { if (this.visible) { this.editor.layoutContentWidget(this); @@ -105,8 +116,11 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, })); const updateFont = () => { + if (!this.domNodes) { + return; + } const fontInfo = this.editor.getOption(EditorOption.fontInfo); - this.element.style.fontSize = `${fontInfo.fontSize}px`; + this.domNodes.element.style.fontSize = `${fontInfo.fontSize}px`; }; updateFont(); @@ -124,13 +138,17 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, return; } - if (!this.element) { + if (!this.domNodes) { this.createParamaterHintDOMNodes(); } this.keyVisible.set(true); this.visible = true; - setTimeout(() => dom.addClass(this.element, 'visible'), 100); + setTimeout(() => { + if (this.domNodes) { + dom.addClass(this.domNodes.element, 'visible'); + } + }, 100); this.editor.layoutContentWidget(this); } @@ -139,14 +157,12 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, return; } - if (!this.element) { - this.createParamaterHintDOMNodes(); - } - this.keyVisible.reset(); this.visible = false; this.announcedLabel = null; - dom.removeClass(this.element, 'visible'); + if (this.domNodes) { + dom.removeClass(this.domNodes.element, 'visible'); + } this.editor.layoutContentWidget(this); } @@ -161,12 +177,16 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, } private render(hints: modes.SignatureHelp): void { + if (!this.domNodes) { + return; + } + const multiple = hints.signatures.length > 1; - dom.toggleClass(this.element, 'multiple', multiple); + dom.toggleClass(this.domNodes.element, 'multiple', multiple); this.keyMultipleSignatures.set(multiple); - this.signature.innerHTML = ''; - this.docs.innerHTML = ''; + this.domNodes.signature.innerHTML = ''; + this.domNodes.docs.innerHTML = ''; const signature = hints.signatures[hints.activeSignature]; @@ -174,7 +194,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, return; } - const code = dom.append(this.signature, $('.code')); + const code = dom.append(this.domNodes.signature, $('.code')); const hasParameters = signature.parameters.length > 0; const fontInfo = this.editor.getOption(EditorOption.fontInfo); @@ -203,17 +223,17 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, this.renderDisposeables.add(renderedContents); documentation.appendChild(renderedContents.element); } - dom.append(this.docs, $('p', {}, documentation)); + dom.append(this.domNodes.docs, $('p', {}, documentation)); } if (signature.documentation === undefined) { /** no op */ } else if (typeof signature.documentation === 'string') { - dom.append(this.docs, $('p', {}, signature.documentation)); + dom.append(this.domNodes.docs, $('p', {}, signature.documentation)); } else { const renderedContents = this.markdownRenderer.render(signature.documentation); dom.addClass(renderedContents.element, 'markdown-docs'); this.renderDisposeables.add(renderedContents); - dom.append(this.docs, renderedContents.element); + dom.append(this.domNodes.docs, renderedContents.element); } let hasDocs = false; @@ -230,8 +250,8 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, hasDocs = true; } - dom.toggleClass(this.signature, 'has-docs', hasDocs); - dom.toggleClass(this.docs, 'empty', !hasDocs); + dom.toggleClass(this.domNodes.signature, 'has-docs', hasDocs); + dom.toggleClass(this.domNodes.docs, 'empty', !hasDocs); let currentOverload = String(hints.activeSignature + 1); @@ -239,7 +259,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, currentOverload += `/${hints.signatures.length}`; } - this.overloads.textContent = currentOverload; + this.domNodes.overloads.textContent = currentOverload; if (activeParameter) { const labelToAnnounce = this.getParameterLabel(signature, hints.activeParameter); @@ -253,7 +273,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, } this.editor.layoutContentWidget(this); - this.scrollbar.scanDomNode(); + this.domNodes.scrollbar.scanDomNode(); } private renderParameters(parent: HTMLElement, signature: modes.SignatureInformation, currentParameter: number): void { @@ -317,7 +337,10 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, } getDomNode(): HTMLElement { - return this.element; + if (!this.domNodes) { + this.createParamaterHintDOMNodes(); + } + return this.domNodes!.element; } getId(): string { @@ -331,10 +354,13 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, } private updateMaxHeight(): void { + if (!this.domNodes) { + return; + } const height = Math.max(this.editor.getLayoutInfo().height / 4, 250); const maxHeight = `${height}px`; - this.element.style.maxHeight = maxHeight; - const wrapper = this.element.getElementsByClassName('wrapper') as HTMLCollectionOf; + this.domNodes.element.style.maxHeight = maxHeight; + const wrapper = this.domNodes.element.getElementsByClassName('wrapper') as HTMLCollectionOf; if (wrapper.length) { wrapper[0].style.maxHeight = maxHeight; } diff --git a/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts b/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts index ca501ed02b..ffa91b3cc0 100644 --- a/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts +++ b/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts @@ -96,24 +96,29 @@ suite('ParameterHintsModel', () => { provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelpResult | Promise { ++invokeCount; - if (invokeCount === 1) { - assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter); - assert.strictEqual(context.triggerCharacter, triggerChar); - assert.strictEqual(context.isRetrigger, false); - assert.strictEqual(context.activeSignatureHelp, undefined); + try { + if (invokeCount === 1) { + assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter); + assert.strictEqual(context.triggerCharacter, triggerChar); + assert.strictEqual(context.isRetrigger, false); + assert.strictEqual(context.activeSignatureHelp, undefined); - // Retrigger - setTimeout(() => editor.trigger('keyboard', Handler.Type, { text: triggerChar }), 50); - } else { - assert.strictEqual(invokeCount, 2); - assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter); - assert.strictEqual(context.isRetrigger, true); - assert.strictEqual(context.triggerCharacter, triggerChar); - assert.strictEqual(context.activeSignatureHelp, emptySigHelp); + // Retrigger + setTimeout(() => editor.trigger('keyboard', Handler.Type, { text: triggerChar }), 50); + } else { + assert.strictEqual(invokeCount, 2); + assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter); + assert.strictEqual(context.isRetrigger, true); + assert.strictEqual(context.triggerCharacter, triggerChar); + assert.strictEqual(context.activeSignatureHelp, emptySigHelp); - done(); + done(); + } + return emptySigHelpResult; + } catch (err) { + console.error(err); + throw err; } - return emptySigHelpResult; } })); @@ -133,25 +138,30 @@ suite('ParameterHintsModel', () => { signatureHelpRetriggerCharacters = []; provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelpResult | Promise { - ++invokeCount; - if (invokeCount === 1) { - assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter); - assert.strictEqual(context.triggerCharacter, triggerChar); - assert.strictEqual(context.isRetrigger, false); - assert.strictEqual(context.activeSignatureHelp, undefined); + try { + ++invokeCount; + if (invokeCount === 1) { + assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter); + assert.strictEqual(context.triggerCharacter, triggerChar); + assert.strictEqual(context.isRetrigger, false); + assert.strictEqual(context.activeSignatureHelp, undefined); - // Cancel and retrigger - hintModel.cancel(); - editor.trigger('keyboard', Handler.Type, { text: triggerChar }); - } else { - assert.strictEqual(invokeCount, 2); - assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter); - assert.strictEqual(context.triggerCharacter, triggerChar); - assert.strictEqual(context.isRetrigger, true); - assert.strictEqual(context.activeSignatureHelp, undefined); - done(); + // Cancel and retrigger + hintModel.cancel(); + editor.trigger('keyboard', Handler.Type, { text: triggerChar }); + } else { + assert.strictEqual(invokeCount, 2); + assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter); + assert.strictEqual(context.triggerCharacter, triggerChar); + assert.strictEqual(context.isRetrigger, true); + assert.strictEqual(context.activeSignatureHelp, undefined); + done(); + } + return emptySigHelpResult; + } catch (err) { + console.error(err); + throw err; } - return emptySigHelpResult; } })); @@ -168,19 +178,24 @@ suite('ParameterHintsModel', () => { signatureHelpRetriggerCharacters = []; provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext) { - ++invokeCount; + try { + ++invokeCount; - assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter); - assert.strictEqual(context.isRetrigger, false); - assert.strictEqual(context.triggerCharacter, 'c'); + assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter); + assert.strictEqual(context.isRetrigger, false); + assert.strictEqual(context.triggerCharacter, 'c'); - // Give some time to allow for later triggers - setTimeout(() => { - assert.strictEqual(invokeCount, 1); + // Give some time to allow for later triggers + setTimeout(() => { + assert.strictEqual(invokeCount, 1); - done(); - }, 50); - return undefined; + done(); + }, 50); + return undefined; + } catch (err) { + console.error(err); + throw err; + } } })); @@ -199,23 +214,28 @@ suite('ParameterHintsModel', () => { signatureHelpRetriggerCharacters = []; provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelpResult | Promise { - ++invokeCount; - if (invokeCount === 1) { - assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter); - assert.strictEqual(context.triggerCharacter, 'a'); + try { + ++invokeCount; + if (invokeCount === 1) { + assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter); + assert.strictEqual(context.triggerCharacter, 'a'); - // retrigger after delay for widget to show up - setTimeout(() => editor.trigger('keyboard', Handler.Type, { text: 'b' }), 50); - } else if (invokeCount === 2) { - assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter); - assert.ok(context.isRetrigger); - assert.strictEqual(context.triggerCharacter, 'b'); - done(); - } else { - assert.fail('Unexpected invoke'); + // retrigger after delay for widget to show up + setTimeout(() => editor.trigger('keyboard', Handler.Type, { text: 'b' }), 50); + } else if (invokeCount === 2) { + assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter); + assert.ok(context.isRetrigger); + assert.strictEqual(context.triggerCharacter, 'b'); + done(); + } else { + assert.fail('Unexpected invoke'); + } + + return emptySigHelpResult; + } catch (err) { + console.error(err); + throw err; } - - return emptySigHelpResult; } })); @@ -234,29 +254,34 @@ suite('ParameterHintsModel', () => { provideSignatureHelp(_model: ITextModel, _position: Position, token: CancellationToken): modes.SignatureHelpResult | Promise { - const count = invokeCount++; - token.onCancellationRequested(() => { didRequestCancellationOf = count; }); + try { + const count = invokeCount++; + token.onCancellationRequested(() => { didRequestCancellationOf = count; }); - // retrigger on first request - if (count === 0) { - hintsModel.trigger({ triggerKind: modes.SignatureHelpTriggerKind.Invoke }, 0); + // retrigger on first request + if (count === 0) { + hintsModel.trigger({ triggerKind: modes.SignatureHelpTriggerKind.Invoke }, 0); + } + + return new Promise(resolve => { + setTimeout(() => { + resolve({ + value: { + signatures: [{ + label: '' + count, + parameters: [] + }], + activeParameter: 0, + activeSignature: 0 + }, + dispose: () => { } + }); + }, 100); + }); + } catch (err) { + console.error(err); + throw err; } - - return new Promise(resolve => { - setTimeout(() => { - resolve({ - value: { - signatures: [{ - label: '' + count, - parameters: [] - }], - activeParameter: 0, - activeSignature: 0 - }, - dispose: () => { } - }); - }, 100); - }); } }; @@ -290,23 +315,28 @@ suite('ParameterHintsModel', () => { signatureHelpRetriggerCharacters = [retriggerChar]; provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelpResult | Promise { - ++invokeCount; - if (invokeCount === 1) { - assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter); - assert.strictEqual(context.triggerCharacter, triggerChar); + try { + ++invokeCount; + if (invokeCount === 1) { + assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter); + assert.strictEqual(context.triggerCharacter, triggerChar); - // retrigger after delay for widget to show up - setTimeout(() => editor.trigger('keyboard', Handler.Type, { text: retriggerChar }), 50); - } else if (invokeCount === 2) { - assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter); - assert.ok(context.isRetrigger); - assert.strictEqual(context.triggerCharacter, retriggerChar); - done(); - } else { - assert.fail('Unexpected invoke'); + // retrigger after delay for widget to show up + setTimeout(() => editor.trigger('keyboard', Handler.Type, { text: retriggerChar }), 50); + } else if (invokeCount === 2) { + assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter); + assert.ok(context.isRetrigger); + assert.strictEqual(context.triggerCharacter, retriggerChar); + done(); + } else { + assert.fail('Unexpected invoke'); + } + + return emptySigHelpResult; + } catch (err) { + console.error(err); + throw err; } - - return emptySigHelpResult; } })); @@ -332,26 +362,31 @@ suite('ParameterHintsModel', () => { signatureHelpRetriggerCharacters = []; async provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): Promise { - if (!context.isRetrigger) { - // retrigger after delay for widget to show up - setTimeout(() => editor.trigger('keyboard', Handler.Type, { text: triggerChar }), 50); + try { + if (!context.isRetrigger) { + // retrigger after delay for widget to show up + setTimeout(() => editor.trigger('keyboard', Handler.Type, { text: triggerChar }), 50); - return { - value: { - activeParameter: 0, - activeSignature: 0, - signatures: [{ - label: firstProviderId, - parameters: [ - { label: paramterLabel } - ] - }] - }, - dispose: () => { } - }; + return { + value: { + activeParameter: 0, + activeSignature: 0, + signatures: [{ + label: firstProviderId, + parameters: [ + { label: paramterLabel } + ] + }] + }, + dispose: () => { } + }; + } + + return undefined; + } catch (err) { + console.error(err); + throw err; } - - return undefined; } })); @@ -390,6 +425,43 @@ suite('ParameterHintsModel', () => { assert.strictEqual(secondHint.activeSignature, 1); assert.strictEqual(secondHint.signatures[0].parameters[0].label, paramterLabel); }); + + test('Quick typing should use the first trigger character', async () => { + const editor = createMockEditor(''); + const model = new ParameterHintsModel(editor, 50); + disposables.add(model); + + const triggerCharacter = 'a'; + + let invokeCount = 0; + disposables.add(modes.SignatureHelpProviderRegistry.register(mockFileSelector, new class implements modes.SignatureHelpProvider { + signatureHelpTriggerCharacters = [triggerCharacter]; + signatureHelpRetriggerCharacters = []; + + provideSignatureHelp(_model: ITextModel, _position: Position, _token: CancellationToken, context: modes.SignatureHelpContext): modes.SignatureHelpResult | Promise { + try { + ++invokeCount; + + if (invokeCount === 1) { + assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter); + assert.strictEqual(context.triggerCharacter, triggerCharacter); + } else { + assert.fail('Unexpected invoke'); + } + + return emptySigHelpResult; + } catch (err) { + console.error(err); + throw err; + } + } + })); + + editor.trigger('keyboard', Handler.Type, { text: triggerCharacter }); + editor.trigger('keyboard', Handler.Type, { text: 'x' }); + + await getNextHint(model); + }); }); function getNextHint(model: ParameterHintsModel) { diff --git a/src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css b/src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css index a39987f8aa..11a1d68eb0 100644 --- a/src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css +++ b/src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css @@ -52,7 +52,7 @@ background-position: center center; } -.monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar .action-label.octicon { +.monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar .action-label.codicon { margin: 0; } @@ -60,3 +60,7 @@ border-top: 1px solid; position: relative; } + +.monaco-editor .peekview-widget .head .peekview-title .codicon { + margin-right: 4px; +} diff --git a/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts b/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts index c4c5e52f84..973c90449b 100644 --- a/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts @@ -57,8 +57,8 @@ export namespace PeekContext { export const notInPeekEditor: ContextKeyExpr = inPeekEditor.toNegated(); } -export function getOuterEditor(accessor: ServicesAccessor): ICodeEditor | undefined { - const editor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); +export function getOuterEditor(accessor: ServicesAccessor): ICodeEditor | null { + let editor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); if (editor instanceof EmbeddedCodeEditorWidget) { return editor.getParentEditor(); } diff --git a/src/vs/editor/contrib/referenceSearch/referenceSearch.ts b/src/vs/editor/contrib/referenceSearch/referenceSearch.ts index da0ba31b20..cfc11e26cc 100644 --- a/src/vs/editor/contrib/referenceSearch/referenceSearch.ts +++ b/src/vs/editor/contrib/referenceSearch/referenceSearch.ts @@ -37,7 +37,7 @@ export const defaultReferenceSearchOptions: RequestOptions = { export class ReferenceController implements editorCommon.IEditorContribution { - private static readonly ID = 'editor.contrib.referenceController'; + public static readonly ID = 'editor.contrib.referenceController'; constructor( editor: ICodeEditor, @@ -50,10 +50,6 @@ export class ReferenceController implements editorCommon.IEditorContribution { public dispose(): void { } - - public getId(): string { - return ReferenceController.ID; - } } export class ReferenceAction extends EditorAction { @@ -93,7 +89,7 @@ export class ReferenceAction extends EditorAction { } } -registerEditorContribution(ReferenceController); +registerEditorContribution(ReferenceController.ID, ReferenceController); registerEditorAction(ReferenceAction); diff --git a/src/vs/editor/contrib/referenceSearch/referencesController.ts b/src/vs/editor/contrib/referenceSearch/referencesController.ts index 60164ff751..1195948b7f 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesController.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesController.ts @@ -30,7 +30,7 @@ export interface RequestOptions { export abstract class ReferencesController implements editorCommon.IEditorContribution { - private static readonly ID = 'editor.contrib.referencesController'; + public static readonly ID = 'editor.contrib.referencesController'; private readonly _disposables = new DisposableStore(); private readonly _editor: ICodeEditor; @@ -59,10 +59,6 @@ export abstract class ReferencesController implements editorCommon.IEditorContri this._referenceSearchVisible = ctxReferenceSearchVisible.bindTo(contextKeyService); } - public getId(): string { - return ReferencesController.ID; - } - public dispose(): void { this._referenceSearchVisible.reset(); dispose(this._disposables); diff --git a/src/vs/editor/contrib/referenceSearch/referencesModel.ts b/src/vs/editor/contrib/referenceSearch/referencesModel.ts index b1928f9f6d..7135aae7c4 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesModel.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesModel.ts @@ -15,6 +15,7 @@ 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'; +import { Constants } from 'vs/base/common/uint'; export class OneReference { readonly id: string; @@ -72,7 +73,7 @@ export class FilePreview implements IDisposable { const { startLineNumber, startColumn, endLineNumber, endColumn } = range; const word = model.getWordUntilPosition({ lineNumber: startLineNumber, column: startColumn - n }); const beforeRange = new Range(startLineNumber, word.startColumn, startLineNumber, startColumn); - const afterRange = new Range(endLineNumber, endColumn, endLineNumber, Number.MAX_VALUE); + const afterRange = new Range(endLineNumber, endColumn, endLineNumber, Constants.MAX_SAFE_SMALL_INTEGER); const before = model.getValueInRange(beforeRange).replace(/^\s+/, ''); const inside = model.getValueInRange(range); diff --git a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts index d79f70aba1..5c66be0691 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts @@ -10,7 +10,7 @@ import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; 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 { basenameOrAuthority, dirname, isEqual } from 'vs/base/common/resources'; import 'vs/css!./media/referencesWidget'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; @@ -66,7 +66,7 @@ class DecorationsManager implements IDisposable { const model = this._editor.getModel(); if (model) { for (const ref of this._model.groups) { - if (ref.uri.toString() === model.uri.toString()) { + if (isEqual(ref.uri, model.uri)) { this._addDecorations(ref); return; } @@ -158,8 +158,8 @@ class DecorationsManager implements IDisposable { } export class LayoutData { - ratio: number = 0.7; - heightInLines: number = 18; + public ratio: number = 0.7; + public heightInLines: number = 18; static fromJSON(raw: string): LayoutData { let ratio: number | undefined; @@ -179,9 +179,9 @@ export class LayoutData { } export interface SelectionEvent { - kind: 'goto' | 'show' | 'side' | 'open'; - source: 'editor' | 'tree' | 'title'; - element?: Location; + readonly kind: 'goto' | 'show' | 'side' | 'open'; + readonly source: 'editor' | 'tree' | 'title'; + readonly element?: Location; } export const ctxReferenceWidgetSearchTreeFocused = new RawContextKey('referenceSearchTreeFocused', true); @@ -366,21 +366,6 @@ export class ReferenceWidget extends PeekViewWidget { this._tree.onDidChangeFocus(e => { onEvent(e.elements[0], 'show'); }); - this._tree.onDidChangeSelection(e => { - let aside = false; - let goto = false; - if (e.browserEvent instanceof KeyboardEvent) { - // todo@joh make this a command - goto = true; - } - if (aside) { - onEvent(e.elements[0], 'side'); - } else if (goto) { - onEvent(e.elements[0], 'goto'); - } else { - onEvent(e.elements[0], 'show'); - } - }); this._tree.onDidOpen(e => { const aside = (e.browserEvent instanceof MouseEvent) && (e.browserEvent.ctrlKey || e.browserEvent.metaKey || e.browserEvent.altKey); let goto = !e.browserEvent || ((e.browserEvent instanceof MouseEvent) && e.browserEvent.detail === 2); diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index 5764cb47c4..47cb4d1b97 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -20,15 +20,15 @@ import { Position, IPosition } from 'vs/editor/common/core/position'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { Range } from 'vs/editor/common/core/range'; import { MessageController } from 'vs/editor/contrib/message/messageController'; -import { EditorState, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState'; +import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/editor/browser/core/editorState'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { URI } from 'vs/base/common/uri'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IdleValue, raceCancellation } from 'vs/base/common/async'; import { withNullAsUndefined } from 'vs/base/common/types'; class RenameSkeleton { @@ -95,21 +95,17 @@ export async function rename(model: ITextModel, position: Position, newName: str // --- register actions and commands -class RenameController extends Disposable implements IEditorContribution { +class RenameController implements IEditorContribution { - private static readonly ID = 'editor.contrib.renameController'; + public static readonly ID = 'editor.contrib.renameController'; - public static get(editor: ICodeEditor): RenameController { + static get(editor: ICodeEditor): RenameController { return editor.getContribution(RenameController.ID); } - private _renameInputField?: RenameInputField; - private _renameOperationIdPool = 1; - - private _activeRename?: { - readonly id: number; - readonly operation: CancelablePromise; - }; + private readonly _renameInputField: IdleValue; + private readonly _dispoableStore = new DisposableStore(); + private _cts: CancellationTokenSource = new CancellationTokenSource(); constructor( private readonly editor: ICodeEditor, @@ -119,37 +115,18 @@ class RenameController extends Disposable implements IEditorContribution { @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IThemeService private readonly _themeService: IThemeService, ) { - super(); - this._register(this.editor.onDidChangeModel(() => this.onModelChanged())); - this._register(this.editor.onDidChangeModelLanguage(() => this.onModelChanged())); - this._register(this.editor.onDidChangeCursorSelection(() => this.onModelChanged())); + this._renameInputField = new IdleValue(() => this._dispoableStore.add(new RenameInputField(this.editor, this._themeService, this._contextKeyService))); } - private get renameInputField(): RenameInputField { - if (!this._renameInputField) { - this._renameInputField = this._register(new RenameInputField(this.editor, this._themeService, this._contextKeyService)); - } - return this._renameInputField; - } - - getId(): string { - return RenameController.ID; + dispose(): void { + this._dispoableStore.dispose(); + this._cts.dispose(true); } async run(): Promise { - if (this._activeRename) { - this._activeRename.operation.cancel(); - } - const id = this._renameOperationIdPool++; - this._activeRename = { - id, - operation: createCancelablePromise(token => this.doRename(token, id)) - }; - return this._activeRename.operation; - } + this._cts.dispose(true); - private async doRename(token: CancellationToken, id: number): Promise { if (!this.editor.hasModel()) { return undefined; } @@ -161,9 +138,12 @@ class RenameController extends Disposable implements IEditorContribution { return undefined; } + this._cts = new EditorStateCancellationTokenSource(this.editor, CodeEditorStateFlag.Position | CodeEditorStateFlag.Value); + + // resolve rename location let loc: RenameLocation & Rejection | undefined; try { - const resolveLocationOperation = skeleton.resolveRenameLocation(token); + const resolveLocationOperation = skeleton.resolveRenameLocation(this._cts.token); this._progressService.showWhile(resolveLocationOperation, 250); loc = await resolveLocationOperation; } catch (e) { @@ -180,10 +160,11 @@ class RenameController extends Disposable implements IEditorContribution { return undefined; } - if (!this._activeRename || this._activeRename.id !== id) { + if (this._cts.token.isCancellationRequested) { return undefined; } + // do rename at location let selection = this.editor.getSelection(); let selectionStart = 0; let selectionEnd = loc.text.length; @@ -193,71 +174,52 @@ class RenameController extends Disposable implements IEditorContribution { selectionEnd = Math.min(loc.range.endColumn, selection.endColumn) - loc.range.startColumn; } - return this.renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd).then(newNameOrFocusFlag => { + const newNameOrFocusFlag = await this._renameInputField.getValue().getInput(loc.range, loc.text, selectionStart, selectionEnd); - if (typeof newNameOrFocusFlag === 'boolean') { - if (newNameOrFocusFlag) { - this.editor.focus(); - } - return undefined; + + if (typeof newNameOrFocusFlag === 'boolean') { + if (newNameOrFocusFlag) { + this.editor.focus(); + } + return undefined; + } + + this.editor.focus(); + + const renameOperation = raceCancellation(skeleton.provideRenameEdits(newNameOrFocusFlag, 0, [], this._cts.token), this._cts.token).then(async renameResult => { + + if (!renameResult || !this.editor.hasModel()) { + return; } - this.editor.focus(); + if (renameResult.rejectReason) { + this._notificationService.info(renameResult.rejectReason); + return; + } - const state = new EditorState(this.editor, CodeEditorStateFlag.Position | CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection | CodeEditorStateFlag.Scroll); + const editResult = await this._bulkEditService.apply(renameResult, { editor: this.editor }); - const renameOperation = Promise.resolve(skeleton.provideRenameEdits(newNameOrFocusFlag, 0, [], token).then(result => { - - if (!this.editor.hasModel()) { - return undefined; - } - - if (result.rejectReason) { - if (state.validate(this.editor)) { - MessageController.get(this.editor).showMessage(result.rejectReason, this.editor.getPosition()); - } else { - this._notificationService.info(result.rejectReason); - } - return undefined; - } - - return this._bulkEditService.apply(result, { editor: this.editor }).then(result => { - // alert - if (result.ariaSummary) { - alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", loc!.text, newNameOrFocusFlag, result.ariaSummary)); - } - }); - - }, err => { - this._notificationService.error(nls.localize('rename.failed', "Rename failed to execute.")); - return Promise.reject(err); - })); - - this._progressService.showWhile(renameOperation, 250); - return renameOperation; + // alert + if (editResult.ariaSummary) { + alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", loc!.text, newNameOrFocusFlag, editResult.ariaSummary)); + } + }, err => { + this._notificationService.error(nls.localize('rename.failed', "Rename failed to execute.")); + return Promise.reject(err); }); + + this._progressService.showWhile(renameOperation, 250); + return renameOperation; + } - public acceptRenameInput(): void { - if (this._renameInputField) { - this._renameInputField.acceptInput(); - } + acceptRenameInput(): void { + this._renameInputField.getValue().acceptInput(); } - public cancelRenameInput(): void { - if (this._renameInputField) { - this._renameInputField.cancelInput(true); - } - } - - private onModelChanged(): void { - if (this._activeRename) { - this._activeRename.operation.cancel(); - this._activeRename = undefined; - - this.cancelRenameInput(); - } + cancelRenameInput(): void { + this._renameInputField.getValue().cancelInput(true); } } @@ -312,7 +274,7 @@ export class RenameAction extends EditorAction { } } -registerEditorContribution(RenameController); +registerEditorContribution(RenameController.ID, RenameController); registerEditorAction(RenameAction); const RenameCommand = EditorCommand.bindToContribution(RenameController.get); diff --git a/src/vs/editor/contrib/rename/renameInputField.ts b/src/vs/editor/contrib/rename/renameInputField.ts index c3344feb74..cd480295e5 100644 --- a/src/vs/editor/contrib/rename/renameInputField.ts +++ b/src/vs/editor/contrib/rename/renameInputField.ts @@ -89,14 +89,14 @@ export class RenameInputField implements IContentWidget, IDisposable { const widgetShadowColor = theme.getColor(widgetShadow); const border = theme.getColor(inputBorder); - this._inputField.style.backgroundColor = background ? background.toString() : null; + this._inputField.style.backgroundColor = background ? background.toString() : ''; this._inputField.style.color = foreground ? foreground.toString() : null; this._inputField.style.borderWidth = border ? '1px' : '0px'; this._inputField.style.borderStyle = border ? 'solid' : 'none'; this._inputField.style.borderColor = border ? border.toString() : 'none'; - this._domNode!.style.boxShadow = widgetShadowColor ? ` 0 2px 8px ${widgetShadowColor}` : null; + this._domNode!.style.boxShadow = widgetShadowColor ? ` 0 2px 8px ${widgetShadowColor}` : ''; } private updateFont(): void { diff --git a/src/vs/editor/contrib/smartSelect/bracketSelections.ts b/src/vs/editor/contrib/smartSelect/bracketSelections.ts index cabec83178..f4ec413c53 100644 --- a/src/vs/editor/contrib/smartSelect/bracketSelections.ts +++ b/src/vs/editor/contrib/smartSelect/bracketSelections.ts @@ -51,7 +51,7 @@ export class BracketSelectionRangeProvider implements SelectionRangeProvider { setTimeout(() => BracketSelectionRangeProvider._bracketsRightYield(resolve, round + 1, model, pos, ranges)); break; } - const key = bracket.close; + const key = bracket.close[0]; if (bracket.isOpen) { // wait for closing let val = counts.has(key) ? counts.get(key)! : 0; @@ -96,7 +96,7 @@ export class BracketSelectionRangeProvider implements SelectionRangeProvider { setTimeout(() => BracketSelectionRangeProvider._bracketsLeftYield(resolve, round + 1, model, pos, ranges, bucket)); break; } - const key = bracket.close; + const key = bracket.close[0]; if (!bracket.isOpen) { // wait for opening let val = counts.has(key) ? counts.get(key)! : 0; diff --git a/src/vs/editor/contrib/smartSelect/smartSelect.ts b/src/vs/editor/contrib/smartSelect/smartSelect.ts index d2fc691d4f..e285feac89 100644 --- a/src/vs/editor/contrib/smartSelect/smartSelect.ts +++ b/src/vs/editor/contrib/smartSelect/smartSelect.ts @@ -47,10 +47,10 @@ class SelectionRanges { class SmartSelectController implements IEditorContribution { - private static readonly _id = 'editor.contrib.smartSelectController'; + public static readonly ID = 'editor.contrib.smartSelectController'; static get(editor: ICodeEditor): SmartSelectController { - return editor.getContribution(SmartSelectController._id); + return editor.getContribution(SmartSelectController.ID); } private readonly _editor: ICodeEditor; @@ -67,10 +67,6 @@ class SmartSelectController implements IEditorContribution { dispose(this._selectionListener); } - getId(): string { - return SmartSelectController._id; - } - run(forward: boolean): Promise | void { if (!this._editor.hasModel()) { return; @@ -210,7 +206,7 @@ class ShrinkSelectionAction extends AbstractSmartSelect { } } -registerEditorContribution(SmartSelectController); +registerEditorContribution(SmartSelectController.ID, SmartSelectController); registerEditorAction(GrowSelectionAction); registerEditorAction(ShrinkSelectionAction); diff --git a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts index 1e0092d15b..0aa8ff2673 100644 --- a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts +++ b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts @@ -12,33 +12,11 @@ import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageCo import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { isLinux, isMacintosh } from 'vs/base/common/platform'; import { BracketSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/bracketSelections'; import { provideSelectionRanges } from 'vs/editor/contrib/smartSelect/smartSelect'; import { CancellationToken } from 'vs/base/common/cancellation'; import { WordSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/wordSelections'; - -class TestTextResourcePropertiesService implements ITextResourcePropertiesService { - - _serviceBrand: undefined; - - constructor( - @IConfigurationService private readonly configurationService: IConfigurationService, - ) { - } - - getEOL(resource: URI | undefined): string { - const filesConfiguration = this.configurationService.getValue<{ eol: string }>('files'); - if (filesConfiguration && filesConfiguration.eol) { - if (filesConfiguration.eol !== 'auto') { - return filesConfiguration.eol; - } - } - return (isLinux || isMacintosh) ? '\n' : '\r\n'; - } -} +import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/modelService.test'; class MockJSMode extends MockMode { diff --git a/src/vs/editor/contrib/snippet/snippetController2.ts b/src/vs/editor/contrib/snippet/snippetController2.ts index 51b9247746..d46c767379 100644 --- a/src/vs/editor/contrib/snippet/snippetController2.ts +++ b/src/vs/editor/contrib/snippet/snippetController2.ts @@ -40,13 +40,15 @@ const _defaultOptions: ISnippetInsertOptions = { export class SnippetController2 implements IEditorContribution { + public static ID = 'snippetController2'; + static get(editor: ICodeEditor): SnippetController2 { - return editor.getContribution('snippetController2'); + return editor.getContribution(SnippetController2.ID); } - static InSnippetMode = new RawContextKey('inSnippetMode', false); - static HasNextTabstop = new RawContextKey('hasNextTabstop', false); - static HasPrevTabstop = new RawContextKey('hasPrevTabstop', false); + static readonly InSnippetMode = new RawContextKey('inSnippetMode', false); + static readonly HasNextTabstop = new RawContextKey('hasNextTabstop', false); + static readonly HasPrevTabstop = new RawContextKey('hasPrevTabstop', false); private readonly _inSnippet: IContextKey; private readonly _hasNextTabstop: IContextKey; @@ -75,10 +77,6 @@ export class SnippetController2 implements IEditorContribution { this._snippetListener.dispose(); } - getId(): string { - return 'snippetController2'; - } - insert( template: string, opts?: Partial @@ -249,7 +247,7 @@ export class SnippetController2 implements IEditorContribution { } -registerEditorContribution(SnippetController2); +registerEditorContribution(SnippetController2.ID, SnippetController2); const CommandCtor = EditorCommand.bindToContribution(SnippetController2.get); diff --git a/src/vs/editor/contrib/snippet/snippetParser.ts b/src/vs/editor/contrib/snippet/snippetParser.ts index 3ebc841708..529860842a 100644 --- a/src/vs/editor/contrib/snippet/snippetParser.ts +++ b/src/vs/editor/contrib/snippet/snippetParser.ts @@ -664,27 +664,23 @@ export class SnippetParser { } private _until(type: TokenType): false | string { - if (this._token.type === TokenType.EOF) { - return false; - } - let res = ''; - let pos = this._token.pos; - let prevToken = { type: TokenType.EOF, pos: 0, len: 0 }; - - while (this._token.type !== type || prevToken.type === TokenType.Backslash) { - if (this._token.type === type) { - res += this._scanner.value.substring(pos, prevToken.pos); - pos = this._token.pos; - } - prevToken = this._token; - this._token = this._scanner.next(); + const start = this._token; + while (this._token.type !== type) { if (this._token.type === TokenType.EOF) { return false; + } else if (this._token.type === TokenType.Backslash) { + const nextToken = this._scanner.next(); + if (nextToken.type !== TokenType.Dollar + && nextToken.type !== TokenType.CurlyClose + && nextToken.type !== TokenType.Backslash) { + return false; + } } + this._token = this._scanner.next(); } - res += this._scanner.value.substring(pos, this._token.pos); + const value = this._scanner.value.substring(start.pos, this._token.pos).replace(/\\(\$|}|\\)/g, '$1'); this._token = this._scanner.next(); - return res; + return value; } private _parse(marker: Marker): boolean { diff --git a/src/vs/editor/contrib/snippet/snippetSession.ts b/src/vs/editor/contrib/snippet/snippetSession.ts index baa2855b2d..9ae417fd36 100644 --- a/src/vs/editor/contrib/snippet/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/snippetSession.ts @@ -18,7 +18,7 @@ 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, Marker } from './snippetParser'; -import { ClipboardBasedVariableResolver, CompositeSnippetVariableResolver, ModelBasedVariableResolver, SelectionBasedVariableResolver, TimeBasedVariableResolver, CommentBasedVariableResolver, WorkspaceBasedVariableResolver } from './snippetVariables'; +import { ClipboardBasedVariableResolver, CompositeSnippetVariableResolver, ModelBasedVariableResolver, SelectionBasedVariableResolver, TimeBasedVariableResolver, CommentBasedVariableResolver, WorkspaceBasedVariableResolver, RandomBasedVariableResolver } from './snippetVariables'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import * as colors from 'vs/platform/theme/common/colorRegistry'; import { withNullAsUndefined } from 'vs/base/common/types'; @@ -392,7 +392,7 @@ export class SnippetSession { const modelBasedVariableResolver = editor.invokeWithinContext(accessor => new ModelBasedVariableResolver(accessor.get(ILabelService, optional), model)); const clipboardService = editor.invokeWithinContext(accessor => accessor.get(IClipboardService, optional)); - clipboardText = clipboardText || clipboardService && clipboardService.readTextSync(); + const readClipboardText = () => clipboardText || clipboardService && clipboardService.readTextSync(); let delta = 0; @@ -445,11 +445,12 @@ export class SnippetSession { snippet.resolveVariables(new CompositeSnippetVariableResolver([ modelBasedVariableResolver, - new ClipboardBasedVariableResolver(clipboardText, idx, indexedSelections.length, editor.getOption(EditorOption.multiCursorPaste) === 'spread'), + new ClipboardBasedVariableResolver(readClipboardText, idx, indexedSelections.length, editor.getOption(EditorOption.multiCursorPaste) === 'spread'), new SelectionBasedVariableResolver(model, selection), new CommentBasedVariableResolver(model), new TimeBasedVariableResolver, new WorkspaceBasedVariableResolver(workspaceService), + new RandomBasedVariableResolver, ])); const offset = model.getOffsetAt(start) + delta; diff --git a/src/vs/editor/contrib/snippet/snippetVariables.ts b/src/vs/editor/contrib/snippet/snippetVariables.ts index 7e2e9a2a3d..14d1d8338c 100644 --- a/src/vs/editor/contrib/snippet/snippetVariables.ts +++ b/src/vs/editor/contrib/snippet/snippetVariables.ts @@ -12,8 +12,10 @@ import { VariableResolver, Variable, Text } from 'vs/editor/contrib/snippet/snip import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { getLeadingWhitespace, commonPrefixLength, isFalsyOrWhitespace, pad, endsWith } from 'vs/base/common/strings'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { isSingleFolderWorkspaceIdentifier, toWorkspaceIdentifier, WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; +import { isSingleFolderWorkspaceIdentifier, toWorkspaceIdentifier, WORKSPACE_EXTENSION, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ILabelService } from 'vs/platform/label/common/label'; +import { normalizeDriveLetter } from 'vs/base/common/labels'; +import { URI } from 'vs/base/common/uri'; export const KnownSnippetVariableNames: { [key: string]: true } = Object.freeze({ 'CURRENT_YEAR': true, @@ -43,6 +45,9 @@ export const KnownSnippetVariableNames: { [key: string]: true } = Object.freeze( 'BLOCK_COMMENT_END': true, 'LINE_COMMENT': true, 'WORKSPACE_NAME': true, + 'WORKSPACE_FOLDER': true, + 'RANDOM': true, + 'RANDOM_HEX': true, }); export class CompositeSnippetVariableResolver implements VariableResolver { @@ -164,10 +169,14 @@ export class ModelBasedVariableResolver implements VariableResolver { } } +export interface IReadClipboardText { + (): string | undefined; +} + export class ClipboardBasedVariableResolver implements VariableResolver { constructor( - private readonly _clipboardText: string | undefined, + private readonly _readClipboardText: IReadClipboardText, private readonly _selectionIdx: number, private readonly _selectionCount: number, private readonly _spread: boolean @@ -180,7 +189,8 @@ export class ClipboardBasedVariableResolver implements VariableResolver { return undefined; } - if (!this._clipboardText) { + const clipboardText = this._readClipboardText(); + if (!clipboardText) { return undefined; } @@ -188,12 +198,12 @@ export class ClipboardBasedVariableResolver implements VariableResolver { // text whenever there the line count equals the cursor count // and when enabled if (this._spread) { - const lines = this._clipboardText.split(/\r\n|\n|\r/).filter(s => !isFalsyOrWhitespace(s)); + const lines = clipboardText.split(/\r\n|\n|\r/).filter(s => !isFalsyOrWhitespace(s)); if (lines.length === this._selectionCount) { return lines[this._selectionIdx]; } } - return this._clipboardText; + return clipboardText; } } export class CommentBasedVariableResolver implements VariableResolver { @@ -267,7 +277,7 @@ export class WorkspaceBasedVariableResolver implements VariableResolver { } resolve(variable: Variable): string | undefined { - if (variable.name !== 'WORKSPACE_NAME' || !this._workspaceService) { + if (!this._workspaceService) { return undefined; } @@ -276,6 +286,15 @@ export class WorkspaceBasedVariableResolver implements VariableResolver { return undefined; } + if (variable.name === 'WORKSPACE_NAME') { + return this._resolveWorkspaceName(workspaceIdentifier); + } else if (variable.name === 'WORKSPACE_FOLDER') { + return this._resoveWorkspacePath(workspaceIdentifier); + } + + return undefined; + } + private _resolveWorkspaceName(workspaceIdentifier: IWorkspaceIdentifier | URI): string | undefined { if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier)) { return path.basename(workspaceIdentifier.path); } @@ -286,4 +305,31 @@ export class WorkspaceBasedVariableResolver implements VariableResolver { } return filename; } + private _resoveWorkspacePath(workspaceIdentifier: IWorkspaceIdentifier | URI): string | undefined { + if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier)) { + return normalizeDriveLetter(workspaceIdentifier.fsPath); + } + + let filename = path.basename(workspaceIdentifier.configPath.path); + let folderpath = workspaceIdentifier.configPath.fsPath; + if (endsWith(folderpath, filename)) { + folderpath = folderpath.substr(0, folderpath.length - filename.length - 1); + } + return (folderpath ? normalizeDriveLetter(folderpath) : '/'); + } +} + +export class RandomBasedVariableResolver implements VariableResolver { + resolve(variable: Variable): string | undefined { + const { name } = variable; + + if (name === 'RANDOM') { + return Math.random().toString().slice(-6); + } + else if (name === 'RANDOM_HEX') { + return Math.random().toString(16).slice(-6); + } + + return undefined; + } } diff --git a/src/vs/editor/contrib/snippet/test/snippetController2.old.test.ts b/src/vs/editor/contrib/snippet/test/snippetController2.old.test.ts index 08fdd58d9d..bba7ae33fe 100644 --- a/src/vs/editor/contrib/snippet/test/snippetController2.old.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetController2.old.test.ts @@ -45,7 +45,7 @@ suite('SnippetController', () => { editor.getModel()!.updateOptions({ insertSpaces: false }); - let snippetController = editor.registerAndInstantiateContribution(TestSnippetController); + let snippetController = editor.registerAndInstantiateContribution(TestSnippetController.ID, TestSnippetController); let template = [ 'for (var ${1:index}; $1 < ${2:array}.length; $1++) {', '\tvar element = $2[$1];', diff --git a/src/vs/editor/contrib/snippet/test/snippetParser.test.ts b/src/vs/editor/contrib/snippet/test/snippetParser.test.ts index 71be413886..a07cdf7f5a 100644 --- a/src/vs/editor/contrib/snippet/test/snippetParser.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetParser.test.ts @@ -767,4 +767,17 @@ suite('SnippetParser', () => { assert.equal((variable.transform!.children[0]).ifValue, 'import { hello } from world'); assert.equal((variable.transform!.children[0]).elseValue, undefined); }); + + test('Snippet escape backslashes inside conditional insertion variable replacement #80394', function () { + + let snippet = new SnippetParser().parse('${CURRENT_YEAR/(.+)/${1:+\\\\}/}'); + let variable = snippet.children[0]; + assert.equal(snippet.children.length, 1); + assert.ok(variable instanceof Variable); + assert.ok(variable.transform); + assert.equal(variable.transform!.children.length, 1); + assert.ok(variable.transform!.children[0] instanceof FormatString); + assert.equal((variable.transform!.children[0]).ifValue, '\\'); + assert.equal((variable.transform!.children[0]).elseValue, undefined); + }); }); diff --git a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts index e855fb161b..33b3946439 100644 --- a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts @@ -236,28 +236,28 @@ suite('Snippet Variables Resolver', function () { test('Add variable to insert value from clipboard to a snippet #40153', function () { - assertVariableResolve(new ClipboardBasedVariableResolver(undefined, 1, 0, true), 'CLIPBOARD', undefined); + assertVariableResolve(new ClipboardBasedVariableResolver(() => undefined, 1, 0, true), 'CLIPBOARD', undefined); - assertVariableResolve(new ClipboardBasedVariableResolver(null!, 1, 0, true), 'CLIPBOARD', undefined); + assertVariableResolve(new ClipboardBasedVariableResolver(() => null!, 1, 0, true), 'CLIPBOARD', undefined); - assertVariableResolve(new ClipboardBasedVariableResolver('', 1, 0, true), 'CLIPBOARD', undefined); + assertVariableResolve(new ClipboardBasedVariableResolver(() => '', 1, 0, true), 'CLIPBOARD', undefined); - assertVariableResolve(new ClipboardBasedVariableResolver('foo', 1, 0, true), 'CLIPBOARD', 'foo'); + assertVariableResolve(new ClipboardBasedVariableResolver(() => 'foo', 1, 0, true), 'CLIPBOARD', 'foo'); - assertVariableResolve(new ClipboardBasedVariableResolver('foo', 1, 0, true), 'foo', undefined); - assertVariableResolve(new ClipboardBasedVariableResolver('foo', 1, 0, true), 'cLIPBOARD', undefined); + assertVariableResolve(new ClipboardBasedVariableResolver(() => 'foo', 1, 0, true), 'foo', undefined); + assertVariableResolve(new ClipboardBasedVariableResolver(() => 'foo', 1, 0, true), 'cLIPBOARD', undefined); }); test('Add variable to insert value from clipboard to a snippet #40153', function () { - assertVariableResolve(new ClipboardBasedVariableResolver('line1', 1, 2, true), 'CLIPBOARD', 'line1'); - assertVariableResolve(new ClipboardBasedVariableResolver('line1\nline2\nline3', 1, 2, true), 'CLIPBOARD', 'line1\nline2\nline3'); + assertVariableResolve(new ClipboardBasedVariableResolver(() => 'line1', 1, 2, true), 'CLIPBOARD', 'line1'); + assertVariableResolve(new ClipboardBasedVariableResolver(() => 'line1\nline2\nline3', 1, 2, true), 'CLIPBOARD', 'line1\nline2\nline3'); - assertVariableResolve(new ClipboardBasedVariableResolver('line1\nline2', 1, 2, true), 'CLIPBOARD', 'line2'); - resolver = new ClipboardBasedVariableResolver('line1\nline2', 0, 2, true); - assertVariableResolve(new ClipboardBasedVariableResolver('line1\nline2', 0, 2, true), 'CLIPBOARD', 'line1'); + assertVariableResolve(new ClipboardBasedVariableResolver(() => 'line1\nline2', 1, 2, true), 'CLIPBOARD', 'line2'); + resolver = new ClipboardBasedVariableResolver(() => 'line1\nline2', 0, 2, true); + assertVariableResolve(new ClipboardBasedVariableResolver(() => 'line1\nline2', 0, 2, true), 'CLIPBOARD', 'line1'); - assertVariableResolve(new ClipboardBasedVariableResolver('line1\nline2', 0, 2, false), 'CLIPBOARD', 'line1\nline2'); + assertVariableResolve(new ClipboardBasedVariableResolver(() => 'line1\nline2', 0, 2, false), 'CLIPBOARD', 'line1\nline2'); }); @@ -296,7 +296,7 @@ suite('Snippet Variables Resolver', function () { assert.equal(snippet.toString(), 'It is not line 10'); }); - test('Add workspace name variable for snippets #68261', function () { + test('Add workspace name and folder variables for snippets #68261', function () { let workspace: IWorkspace; let resolver: VariableResolver; @@ -319,14 +319,21 @@ suite('Snippet Variables Resolver', function () { // empty workspace workspace = new Workspace(''); assertVariableResolve(resolver, 'WORKSPACE_NAME', undefined); + assertVariableResolve(resolver, 'WORKSPACE_FOLDER', undefined); // single folder workspace without config workspace = new Workspace('', [toWorkspaceFolder(URI.file('/folderName'))]); assertVariableResolve(resolver, 'WORKSPACE_NAME', 'folderName'); + if (!isWindows) { + assertVariableResolve(resolver, 'WORKSPACE_FOLDER', '/folderName'); + } // workspace with config const workspaceConfigPath = URI.file('testWorkspace.code-workspace'); workspace = new Workspace('', toWorkspaceFolders([{ path: 'folderName' }], workspaceConfigPath), workspaceConfigPath); assertVariableResolve(resolver, 'WORKSPACE_NAME', 'testWorkspace'); + if (!isWindows) { + assertVariableResolve(resolver, 'WORKSPACE_FOLDER', '/'); + } }); }); diff --git a/src/vs/editor/contrib/suggest/suggestAlternatives.ts b/src/vs/editor/contrib/suggest/suggestAlternatives.ts index 5cdc3e9e8b..7c40d67c04 100644 --- a/src/vs/editor/contrib/suggest/suggestAlternatives.ts +++ b/src/vs/editor/contrib/suggest/suggestAlternatives.ts @@ -11,7 +11,7 @@ import { ISelectedSuggestion } from './suggestWidget'; export class SuggestAlternatives { - static OtherSuggestions = new RawContextKey('hasOtherSuggestions', false); + static readonly OtherSuggestions = new RawContextKey('hasOtherSuggestions', false); private readonly _ckOtherSuggestions: IContextKey; diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index 9351753613..61c1cafff3 100644 --- a/src/vs/editor/contrib/suggest/suggestController.ts +++ b/src/vs/editor/contrib/suggest/suggestController.ts @@ -19,10 +19,10 @@ import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2 import { SnippetParser } from 'vs/editor/contrib/snippet/snippetParser'; import { ISuggestMemoryService } from 'vs/editor/contrib/suggest/suggestMemory'; import * as nls from 'vs/nls'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Context as SuggestContext, CompletionItem } from './suggest'; import { SuggestAlternatives } from './suggestAlternatives'; import { State, SuggestModel } from './suggestModel'; @@ -86,9 +86,16 @@ class LineSuffix { } } +const enum InsertFlags { + NoBeforeUndoStop = 1, + NoAfterUndoStop = 2, + KeepAlternativeSuggestions = 4, + AlternativeOverwriteConfig = 8 +} + export class SuggestController implements IEditorContribution { - private static readonly ID: string = 'editor.contrib.suggestController'; + public static readonly ID: string = 'editor.contrib.suggestController'; public static get(editor: ICodeEditor): SuggestController { return editor.getContribution(SuggestController.ID); @@ -115,10 +122,10 @@ export class SuggestController implements IEditorContribution { const widget = this._instantiationService.createInstance(SuggestWidget, this._editor); this._toDispose.add(widget); - this._toDispose.add(widget.onDidSelect(item => this._insertSuggestion(item, false, true), this)); + this._toDispose.add(widget.onDidSelect(item => this._insertSuggestion(item, 0), this)); // Wire up logic to accept a suggestion on certain characters - const commitCharacterController = new CommitCharacterController(this._editor, widget, item => this._insertSuggestion(item, false, true)); + const commitCharacterController = new CommitCharacterController(this._editor, widget, item => this._insertSuggestion(item, InsertFlags.NoAfterUndoStop)); this._toDispose.add(commitCharacterController); this._toDispose.add(this._model.onDidSuggest(e => { if (e.completionModel.items.length === 0) { @@ -210,11 +217,6 @@ export class SuggestController implements IEditorContribution { updateFromConfig(); } - - getId(): string { - return SuggestController.ID; - } - dispose(): void { this._alternatives.dispose(); this._toDispose.dispose(); @@ -223,7 +225,10 @@ export class SuggestController implements IEditorContribution { this._lineSuffix.dispose(); } - protected _insertSuggestion(event: ISelectedSuggestion | undefined, keepAlternativeSuggestions: boolean, undoStops: boolean): void { + protected _insertSuggestion( + event: ISelectedSuggestion | undefined, + flags: InsertFlags + ): void { if (!event || !event.item) { this._alternatives.getValue().reset(); this._model.cancel(); @@ -237,12 +242,11 @@ export class SuggestController implements IEditorContribution { const model = this._editor.getModel(); const modelVersionNow = model.getAlternativeVersionId(); const { completion: suggestion, position } = event.item; - const editorColumn = this._editor.getPosition().column; - const columnDelta = editorColumn - position.column; + const columnDelta = this._editor.getPosition().column - position.column; // pushing undo stops *before* additional text edits and // *after* the main edit - if (undoStops) { + if (!(flags & InsertFlags.NoBeforeUndoStop)) { this._editor.pushUndoStop(); } @@ -258,9 +262,26 @@ export class SuggestController implements IEditorContribution { insertText = SnippetParser.escape(insertText); } - const overwriteBefore = position.column - suggestion.range.startColumn; - const overwriteAfter = suggestion.range.endColumn - position.column; - const suffixDelta = this._lineSuffix.value ? this._lineSuffix.value.delta(this._editor.getPosition()) : 0; + let overwriteBefore = position.column - suggestion.range.startColumn; + let overwriteAfter = suggestion.range.endColumn - position.column; + let suffixDelta = this._lineSuffix.value ? this._lineSuffix.value.delta(this._editor.getPosition()) : 0; + let word = model.getWordAtPosition(this._editor.getPosition()); + + const overwriteConfig = flags & InsertFlags.AlternativeOverwriteConfig + ? !this._editor.getOption(EditorOption.suggest).overwriteOnAccept + : this._editor.getOption(EditorOption.suggest).overwriteOnAccept; + if (!overwriteConfig) { + if (overwriteAfter > 0 && word && suggestion.range.endColumn === word.endColumn) { + // don't overwrite anything right of the cursor, overrule extension even when the + // completion only replaces a word... + overwriteAfter = 0; + } + } else { + if (overwriteAfter === 0 && word) { + // compute fallback overwrite length + overwriteAfter = word.endColumn - this._editor.getPosition().column; + } + } SnippetController2.get(this._editor).insert(insertText, { overwriteBefore: overwriteBefore + columnDelta, @@ -270,7 +291,7 @@ export class SuggestController implements IEditorContribution { adjustWhitespace: !(suggestion.insertTextRules! & CompletionItemInsertTextRule.KeepWhitespace) }); - if (undoStops) { + if (!(flags & InsertFlags.NoAfterUndoStop)) { this._editor.pushUndoStop(); } @@ -291,7 +312,7 @@ export class SuggestController implements IEditorContribution { this._model.cancel(); } - if (keepAlternativeSuggestions) { + if (flags & InsertFlags.KeepAlternativeSuggestions) { this._alternatives.getValue().set(event, next => { // this is not so pretty. when inserting the 'next' // suggestion we undo until we are at the state at @@ -300,7 +321,10 @@ export class SuggestController implements IEditorContribution { if (modelVersionNow !== model.getAlternativeVersionId()) { model.undo(); } - this._insertSuggestion(next, false, false); + this._insertSuggestion( + next, + InsertFlags.NoBeforeUndoStop | InsertFlags.NoAfterUndoStop | (flags & InsertFlags.AlternativeOverwriteConfig ? InsertFlags.AlternativeOverwriteConfig : 0) + ); break; } }); @@ -382,7 +406,7 @@ export class SuggestController implements IEditorContribution { return; } this._editor.pushUndoStop(); - this._insertSuggestion({ index, item, model: completionModel }, true, false); + this._insertSuggestion({ index, item, model: completionModel }, InsertFlags.KeepAlternativeSuggestions | InsertFlags.NoBeforeUndoStop | InsertFlags.NoAfterUndoStop); }, undefined, listener); }); @@ -392,9 +416,16 @@ export class SuggestController implements IEditorContribution { this._editor.focus(); } - acceptSelectedSuggestion(keepAlternativeSuggestions?: boolean): void { + acceptSelectedSuggestion(keepAlternativeSuggestions: boolean, alternativeOverwriteConfig: boolean): void { const item = this._widget.getValue().getFocusedItem(); - this._insertSuggestion(item, !!keepAlternativeSuggestions, true); + let flags = 0; + if (keepAlternativeSuggestions) { + flags |= InsertFlags.KeepAlternativeSuggestions; + } + if (alternativeOverwriteConfig) { + flags |= InsertFlags.AlternativeOverwriteConfig; + } + this._insertSuggestion(item, flags); } acceptNextSuggestion() { @@ -478,7 +509,7 @@ export class TriggerSuggestAction extends EditorAction { } } -registerEditorContribution(SuggestController); +registerEditorContribution(SuggestController.ID, SuggestController); registerEditorAction(TriggerSuggestAction); const weight = KeybindingWeight.EditorContrib + 90; @@ -489,24 +520,43 @@ const SuggestCommand = EditorCommand.bindToContribution(Sugge registerEditorCommand(new SuggestCommand({ id: 'acceptSelectedSuggestion', precondition: SuggestContext.Visible, - handler: x => x.acceptSelectedSuggestion(true), - kbOpts: { - weight: weight, - kbExpr: EditorContextKeys.textInputFocus, - primary: KeyCode.Tab + handler(x, args) { + const alternative: boolean = typeof args === 'object' && typeof args.alternative === 'boolean' + ? args.alternative + : false; + x.acceptSelectedSuggestion(true, alternative); } })); -registerEditorCommand(new SuggestCommand({ - id: 'acceptSelectedSuggestionOnEnter', - precondition: SuggestContext.Visible, - handler: x => x.acceptSelectedSuggestion(false), - kbOpts: { - weight: weight, - kbExpr: ContextKeyExpr.and(EditorContextKeys.textInputFocus, SuggestContext.AcceptSuggestionsOnEnter, SuggestContext.MakesTextEdit), - primary: KeyCode.Enter - } -})); +// normal tab +KeybindingsRegistry.registerKeybindingRule({ + id: 'acceptSelectedSuggestion', + when: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus), + primary: KeyCode.Tab, + weight +}); + +// accept on enter has special rules +KeybindingsRegistry.registerKeybindingRule({ + id: 'acceptSelectedSuggestion', + when: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus, SuggestContext.AcceptSuggestionsOnEnter, SuggestContext.MakesTextEdit), + primary: KeyCode.Enter, + weight +}); + +// shift+enter and shift+tab use the alternative-flag so that the suggest controller +// is doing the opposite of the editor.suggest.overwriteOnAccept-configuration +KeybindingsRegistry.registerKeybindingRule({ + id: 'acceptSelectedSuggestion', + when: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus), + primary: KeyMod.Shift | KeyCode.Tab, + secondary: [KeyMod.Shift | KeyCode.Enter], + args: { alternative: true }, + weight +}); + +// continue to support the old command +CommandsRegistry.registerCommandAlias('acceptSelectedSuggestionOnEnter', 'acceptSelectedSuggestion'); registerEditorCommand(new SuggestCommand({ id: 'hideSuggestWidget', diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index d7a82c1f0b..84b3eba2cc 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -116,7 +116,7 @@ class Renderer implements IListRenderer const text = append(container, $('.contents')); const main = append(text, $('.main')); - data.iconLabel = new IconLabel(main, { supportHighlights: true, supportOcticons: true }); + data.iconLabel = new IconLabel(main, { supportHighlights: true, supportCodicons: true }); data.disposables.add(data.iconLabel); data.typeLabel = append(main, $('span.type-label')); @@ -174,10 +174,9 @@ class Renderer implements IListRenderer } else if (suggestion.kind === CompletionItemKind.File && this._themeService.getIconTheme().hasFileIcons) { // special logic for 'file' completion items data.icon.className = 'icon hide'; - labelOptions.extraClasses = flatten([ - getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: suggestion.label }), FileKind.FILE), - getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: suggestion.detail }), FileKind.FILE) - ]); + const labelClasses = getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: suggestion.label }), FileKind.FILE); + const detailClasses = getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: suggestion.detail }), FileKind.FILE); + labelOptions.extraClasses = labelClasses.length > detailClasses.length ? labelClasses : detailClasses; } else if (suggestion.kind === CompletionItemKind.Folder && this._themeService.getIconTheme().hasFolderIcons) { // special logic for 'folder' completion items diff --git a/src/vs/editor/contrib/suggest/test/completionModel.test.ts b/src/vs/editor/contrib/suggest/test/completionModel.test.ts index de6c707e44..8457f04f8b 100644 --- a/src/vs/editor/contrib/suggest/test/completionModel.test.ts +++ b/src/vs/editor/contrib/suggest/test/completionModel.test.ts @@ -159,6 +159,7 @@ suite('CompletionModel', function () { leadingLineContent: 's', characterCountDelta: 0 }, WordDistance.None, { + overwriteOnAccept: false, snippetsPreventQuickSuggestions: true, filterGraceful: true, localityBonus: false, @@ -186,6 +187,7 @@ suite('CompletionModel', function () { leadingLineContent: 's', characterCountDelta: 0 }, WordDistance.None, { + overwriteOnAccept: false, snippetsPreventQuickSuggestions: true, filterGraceful: true, localityBonus: false, @@ -212,6 +214,7 @@ suite('CompletionModel', function () { leadingLineContent: 's', characterCountDelta: 0 }, WordDistance.None, { + overwriteOnAccept: false, snippetsPreventQuickSuggestions: true, filterGraceful: true, localityBonus: false, diff --git a/src/vs/editor/contrib/suggest/test/suggestModel.test.ts b/src/vs/editor/contrib/suggest/test/suggestModel.test.ts index eac7f0821f..c57516c4c2 100644 --- a/src/vs/editor/contrib/suggest/test/suggestModel.test.ts +++ b/src/vs/editor/contrib/suggest/test/suggestModel.test.ts @@ -59,7 +59,7 @@ function createMockEditor(model: TextModel): TestCodeEditor { }], ), }); - editor.registerAndInstantiateContribution(SnippetController2); + editor.registerAndInstantiateContribution(SnippetController2.ID, SnippetController2); return editor; } @@ -675,12 +675,12 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { return withOracle(async (sugget, editor) => { class TestCtrl extends SuggestController { - _insertSuggestion(item: ISelectedSuggestion) { - super._insertSuggestion(item, false, true); + _insertSuggestion(item: ISelectedSuggestion, flags: number = 0) { + super._insertSuggestion(item, flags); } } - const ctrl = editor.registerAndInstantiateContribution(TestCtrl); - editor.registerAndInstantiateContribution(SnippetController2); + const ctrl = editor.registerAndInstantiateContribution(TestCtrl.ID, TestCtrl); + editor.registerAndInstantiateContribution(SnippetController2.ID, SnippetController2); await assertEvent(sugget.onDidSuggest, () => { editor.setPosition({ lineNumber: 1, column: 3 }); diff --git a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts index 029aa208b2..262d8ba826 100644 --- a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts @@ -458,7 +458,7 @@ class WordHighlighter { class WordHighlighterContribution extends Disposable implements editorCommon.IEditorContribution { - private static readonly ID = 'editor.contrib.wordHighlighter'; + public static readonly ID = 'editor.contrib.wordHighlighter'; public static get(editor: ICodeEditor): WordHighlighterContribution { return editor.getContribution(WordHighlighterContribution.ID); @@ -484,10 +484,6 @@ class WordHighlighterContribution extends Disposable implements editorCommon.IEd createWordHighlighterIfPossible(); } - public getId(): string { - return WordHighlighterContribution.ID; - } - public saveViewState(): boolean { if (this.wordHighligher && this.wordHighligher.hasDecorations()) { return true; @@ -603,7 +599,7 @@ class TriggerWordHighlightAction extends EditorAction { } } -registerEditorContribution(WordHighlighterContribution); +registerEditorContribution(WordHighlighterContribution.ID, WordHighlighterContribution); registerEditorAction(NextWordHighlightAction); registerEditorAction(PrevWordHighlightAction); registerEditorAction(TriggerWordHighlightAction); diff --git a/src/vs/editor/contrib/zoneWidget/zoneWidget.ts b/src/vs/editor/contrib/zoneWidget/zoneWidget.ts index 3ff9cee13b..24f8273210 100644 --- a/src/vs/editor/contrib/zoneWidget/zoneWidget.ts +++ b/src/vs/editor/contrib/zoneWidget/zoneWidget.ts @@ -50,11 +50,11 @@ const WIDGET_ID = 'vs.editor.contrib.zoneWidget'; export class ViewZoneDelegate implements IViewZone { - public domNode: HTMLElement; - public id: string = ''; // A valid zone id should be greater than 0 - public afterLineNumber: number; - public afterColumn: number; - public heightInLines: number; + domNode: HTMLElement; + id: string = ''; // A valid zone id should be greater than 0 + afterLineNumber: number; + afterColumn: number; + heightInLines: number; private readonly _onDomNodeTop: (top: number) => void; private readonly _onComputedHeight: (height: number) => void; @@ -71,11 +71,11 @@ export class ViewZoneDelegate implements IViewZone { this._onComputedHeight = onComputedHeight; } - public onDomNodeTop(top: number): void { + onDomNodeTop(top: number): void { this._onDomNodeTop(top); } - public onComputedHeight(height: number): void { + onComputedHeight(height: number): void { this._onComputedHeight(height); } } @@ -90,15 +90,15 @@ export class OverlayWidgetDelegate implements IOverlayWidget { this._domNode = domNode; } - public getId(): string { + getId(): string { return this._id; } - public getDomNode(): HTMLElement { + getDomNode(): HTMLElement { return this._domNode; } - public getPosition(): IOverlayWidgetPosition | null { + getPosition(): IOverlayWidgetPosition | null { return null; } } @@ -167,10 +167,10 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { protected _viewZone: ViewZoneDelegate | null = null; protected readonly _disposables = new DisposableStore(); - public container: HTMLElement | null = null; - public domNode: HTMLElement; - public editor: ICodeEditor; - public options: IOptions; + container: HTMLElement | null = null; + domNode: HTMLElement; + editor: ICodeEditor; + options: IOptions; constructor(editor: ICodeEditor, options: IOptions = {}) { @@ -191,7 +191,7 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { })); } - public dispose(): void { + dispose(): void { if (this._overlayWidget) { this.editor.removeOverlayWidget(this._overlayWidget); this._overlayWidget = null; @@ -212,7 +212,7 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { this._disposables.dispose(); } - public create(): void { + create(): void { dom.addClass(this.domNode, 'zone-widget'); if (this.options.className) { @@ -231,7 +231,7 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { this._applyStyles(); } - public style(styles: IStyles): void { + style(styles: IStyles): void { if (styles.frameColor) { this.options.frameColor = styles.frameColor; } @@ -284,7 +284,7 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { } } - public get position(): Position | undefined { + get position(): Position | undefined { const [id] = this._positionMarkerId; if (!id) { return undefined; @@ -304,18 +304,15 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { protected _isShowing: boolean = false; - public show(rangeOrPos: IRange | IPosition, heightInLines: number): void { - const range = Range.isIRange(rangeOrPos) - ? rangeOrPos - : new Range(rangeOrPos.lineNumber, rangeOrPos.column, rangeOrPos.lineNumber, rangeOrPos.column); - + show(rangeOrPos: IRange | IPosition, heightInLines: number): void { + const range = Range.isIRange(rangeOrPos) ? Range.lift(rangeOrPos) : Range.fromPositions(rangeOrPos); this._isShowing = true; this._showImpl(range, heightInLines); this._isShowing = false; this._positionMarkerId = this.editor.deltaDecorations(this._positionMarkerId, [{ range, options: ModelDecorationOptions.EMPTY }]); } - public hide(): void { + hide(): void { if (this._viewZone) { this.editor.changeViewZones(accessor => { if (this._viewZone) { @@ -350,12 +347,8 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { return result; } - private _showImpl(where: IRange, heightInLines: number): void { - const position = { - lineNumber: where.startLineNumber, - column: where.startColumn - }; - + private _showImpl(where: Range, heightInLines: number): void { + const position = where.getStartPosition(); const layoutInfo = this.editor.getLayoutInfo(); const width = this._getWidth(layoutInfo); this.domNode.style.width = `${width}px`; @@ -432,14 +425,23 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { const model = this.editor.getModel(); if (model) { - // Reveal the line above or below the zone widget, to get the zone widget in the viewport - const revealLineNumber = Math.min(model.getLineCount(), Math.max(1, where.endLineNumber + 1)); - this.revealLine(revealLineNumber); + const revealLine = where.endLineNumber + 1; + if (revealLine <= model.getLineCount()) { + // reveal line below the zone widget + this.revealLine(revealLine, false); + } else { + // reveal last line atop + this.revealLine(model.getLineCount(), true); + } } } - protected revealLine(lineNumber: number) { - this.editor.revealLine(lineNumber, ScrollType.Smooth); + protected revealLine(lineNumber: number, isLastLine: boolean) { + if (isLastLine) { + this.editor.revealLineInCenter(lineNumber, ScrollType.Smooth); + } else { + this.editor.revealLine(lineNumber, ScrollType.Smooth); + } } protected setCssClass(className: string, classToReplace?: string): void { diff --git a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts index 066393ff22..008497ec83 100644 --- a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts +++ b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts @@ -37,7 +37,7 @@ const CONTEXT_ACCESSIBILITY_WIDGET_VISIBLE = new RawContextKey('accessi class AccessibilityHelpController extends Disposable implements IEditorContribution { - private static readonly ID = 'editor.contrib.accessibilityHelpController'; + public static readonly ID = 'editor.contrib.accessibilityHelpController'; public static get(editor: ICodeEditor): AccessibilityHelpController { return editor.getContribution( @@ -60,10 +60,6 @@ class AccessibilityHelpController extends Disposable ); } - public getId(): string { - return AccessibilityHelpController.ID; - } - public show(): void { this._widget.show(); } @@ -348,7 +344,7 @@ class ShowAccessibilityHelpAction extends EditorAction { } } -registerEditorContribution(AccessibilityHelpController); +registerEditorContribution(AccessibilityHelpController.ID, AccessibilityHelpController); registerEditorAction(ShowAccessibilityHelpAction); const AccessibilityHelpCommand = EditorCommand.bindToContribution(AccessibilityHelpController.get); diff --git a/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts b/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts index a6442672b6..19a9da4157 100644 --- a/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts +++ b/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts @@ -14,7 +14,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; export class IPadShowKeyboard extends Disposable implements IEditorContribution { - private static readonly ID = 'editor.contrib.iPadShowKeyboard'; + public static readonly ID = 'editor.contrib.iPadShowKeyboard'; private readonly editor: ICodeEditor; private widget: ShowKeyboardWidget | null; @@ -44,10 +44,6 @@ export class IPadShowKeyboard extends Disposable implements IEditorContribution } } - public getId(): string { - return IPadShowKeyboard.ID; - } - public dispose(): void { super.dispose(); if (this.widget) { @@ -103,4 +99,4 @@ class ShowKeyboardWidget extends Disposable implements IOverlayWidget { } } -registerEditorContribution(IPadShowKeyboard); +registerEditorContribution(IPadShowKeyboard.ID, IPadShowKeyboard); diff --git a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts index a581e0b8a1..3b75c9a16b 100644 --- a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts +++ b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts @@ -25,7 +25,7 @@ import { InspectTokensNLS } from 'vs/editor/common/standaloneStrings'; class InspectTokensController extends Disposable implements IEditorContribution { - private static readonly ID = 'editor.contrib.inspectTokens'; + public static readonly ID = 'editor.contrib.inspectTokens'; public static get(editor: ICodeEditor): InspectTokensController { return editor.getContribution(InspectTokensController.ID); @@ -50,10 +50,6 @@ class InspectTokensController extends Disposable implements IEditorContribution this._register(TokenizationRegistry.onDidChange((e) => this.stop())); } - public getId(): string { - return InspectTokensController.ID; - } - public dispose(): void { this.stop(); super.dispose(); @@ -325,7 +321,7 @@ class InspectTokensWidget extends Disposable implements IContentWidget { } } -registerEditorContribution(InspectTokensController); +registerEditorContribution(InspectTokensController.ID, InspectTokensController); registerEditorAction(InspectTokens); registerThemingParticipant((theme, collector) => { diff --git a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts b/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts index a0c80d9c9e..5356ce6053 100644 --- a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts +++ b/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts @@ -24,7 +24,7 @@ export interface IQuickOpenControllerOpts { export class QuickOpenController implements editorCommon.IEditorContribution, IDecorator { - private static readonly ID = 'editor.controller.quickOpenController'; + public static readonly ID = 'editor.controller.quickOpenController'; public static get(editor: ICodeEditor): QuickOpenController { return editor.getContribution(QuickOpenController.ID); @@ -39,10 +39,6 @@ export class QuickOpenController implements editorCommon.IEditorContribution, ID this.editor = editor; } - public getId(): string { - return QuickOpenController.ID; - } - public dispose(): void { // Dispose widget if (this.widget) { @@ -173,4 +169,4 @@ export interface IDecorator { clearDecorations(): void; } -registerEditorContribution(QuickOpenController); +registerEditorContribution(QuickOpenController.ID, QuickOpenController); diff --git a/src/vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget.ts b/src/vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget.ts index 62c683a556..d9e06fed03 100644 --- a/src/vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget.ts +++ b/src/vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget.ts @@ -23,7 +23,7 @@ export class QuickOpenEditorWidget implements IOverlayWidget { private readonly codeEditor: ICodeEditor; private readonly themeService: IThemeService; - private visible: boolean; + private visible: boolean | undefined; private quickOpenWidget: QuickOpenWidget; private domNode: HTMLElement; private styler: IDisposable; @@ -31,11 +31,8 @@ export class QuickOpenEditorWidget implements IOverlayWidget { constructor(codeEditor: ICodeEditor, onOk: () => void, onCancel: () => void, onType: (value: string) => void, configuration: IQuickOpenEditorWidgetOptions, themeService: IThemeService) { this.codeEditor = codeEditor; this.themeService = themeService; + this.visible = false; - this.create(onOk, onCancel, onType, configuration); - } - - private create(onOk: () => void, onCancel: () => void, onType: (value: string) => void, configuration: IQuickOpenEditorWidgetOptions): void { this.domNode = document.createElement('div'); this.quickOpenWidget = new QuickOpenWidget( @@ -58,29 +55,29 @@ export class QuickOpenEditorWidget implements IOverlayWidget { this.codeEditor.addOverlayWidget(this); } - public setInput(model: QuickOpenModel, focus: IAutoFocus): void { + setInput(model: QuickOpenModel, focus: IAutoFocus): void { this.quickOpenWidget.setInput(model, focus); } - public getId(): string { + getId(): string { return QuickOpenEditorWidget.ID; } - public getDomNode(): HTMLElement { + getDomNode(): HTMLElement { return this.domNode; } - public destroy(): void { + destroy(): void { this.codeEditor.removeOverlayWidget(this); this.quickOpenWidget.dispose(); this.styler.dispose(); } - public isVisible(): boolean { - return this.visible; + isVisible(): boolean { + return !!this.visible; } - public show(value: string): void { + show(value: string): void { this.visible = true; const editorLayout = this.codeEditor.getLayoutInfo(); @@ -92,13 +89,13 @@ export class QuickOpenEditorWidget implements IOverlayWidget { this.codeEditor.layoutOverlayWidget(this); } - public hide(): void { + hide(): void { this.visible = false; this.quickOpenWidget.hide(); this.codeEditor.layoutOverlayWidget(this); } - public getPosition(): IOverlayWidgetPosition | null { + getPosition(): IOverlayWidgetPosition | null { if (this.visible) { return { preference: OverlayWidgetPositionPreference.TOP_CENTER @@ -107,4 +104,4 @@ export class QuickOpenEditorWidget implements IOverlayWidget { return null; } -} \ No newline at end of file +} diff --git a/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts b/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts index ffc0e34367..580e0ce766 100644 --- a/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts +++ b/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts @@ -15,7 +15,7 @@ import { ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editor import { IRange, Range } from 'vs/editor/common/core/range'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { DocumentSymbol, DocumentSymbolProviderRegistry, symbolKindToCssClass } from 'vs/editor/common/modes'; +import { DocumentSymbol, DocumentSymbolProviderRegistry, SymbolKinds } from 'vs/editor/common/modes'; import { getDocumentSymbols } from 'vs/editor/contrib/quickOpen/quickOpen'; import { BaseEditorQuickOpenAction, IDecorator } from 'vs/editor/standalone/browser/quickOpen/editorQuickOpen'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -198,7 +198,7 @@ export class QuickOutlineAction extends BaseEditorQuickOpenAction { } // Add - results.push(this.symbolEntry(label, symbolKindToCssClass(element.kind), description, element.range, highlights, editor, controller)); + results.push(this.symbolEntry(label, SymbolKinds.toCssClassName(element.kind), description, element.range, highlights, editor, controller)); } } diff --git a/src/vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch.ts b/src/vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch.ts index 40b1c4257d..df140715d4 100644 --- a/src/vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch.ts +++ b/src/vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch.ts @@ -37,4 +37,4 @@ export class StandaloneReferencesController extends ReferencesController { } } -registerEditorContribution(StandaloneReferencesController); +registerEditorContribution(ReferencesController.ID, StandaloneReferencesController); diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index e0367f0d60..7dbe8f57c5 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -37,7 +37,7 @@ 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, IStatusMessageOptions } from 'vs/platform/notification/common/notification'; +import { INotification, INotificationHandle, INotificationService, IPromptChoice, IPromptOptions, NoOpNotification, IStatusMessageOptions, NotificationsFilter } from 'vs/platform/notification/common/notification'; import { IProgressRunner, IEditorProgressService } 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'; @@ -233,6 +233,8 @@ export class SimpleNotificationService implements INotificationService { public status(message: string | Error, options?: IStatusMessageOptions): IDisposable { return Disposable.None; } + + public setFilter(filter: NotificationsFilter): void { } } export class StandaloneCommandService implements ICommandService { @@ -515,12 +517,10 @@ export class SimpleResourcePropertiesService implements ITextResourcePropertiesS ) { } - getEOL(resource: URI): string { - const filesConfiguration = this.configurationService.getValue<{ eol: string }>('files'); - if (filesConfiguration && filesConfiguration.eol) { - if (filesConfiguration.eol !== 'auto') { - return filesConfiguration.eol; - } + getEOL(resource: URI, language?: string): string { + const eol = this.configurationService.getValue('files.eol', { overrideIdentifier: language, resource }); + if (eol && eol !== 'auto') { + return eol; } return (isLinux || isMacintosh) ? '\n' : '\r\n'; } @@ -551,7 +551,7 @@ export class SimpleWorkspaceContextService implements IWorkspaceContextService { public _serviceBrand: undefined; - private static SCHEME = 'inmemory'; + private static readonly SCHEME = 'inmemory'; private readonly _onDidChangeWorkspaceName = new Emitter(); public readonly onDidChangeWorkspaceName: Event = this._onDidChangeWorkspaceName.event; diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 0fcc1c12d1..ae60a6231d 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -30,6 +30,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { StandaloneCodeEditorNLS } from 'vs/editor/common/standaloneStrings'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IEditorProgressService } from 'vs/platform/progress/common/progress'; /** * Description of an action contribution @@ -376,6 +377,7 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon @INotificationService notificationService: INotificationService, @IConfigurationService configurationService: IConfigurationService, @IContextMenuService contextMenuService: IContextMenuService, + @IEditorProgressService editorProgressService: IEditorProgressService, @optional(IClipboardService) clipboardService: IClipboardService | null, ) { applyConfigurationValues(configurationService, options, true); @@ -384,7 +386,7 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon options.theme = themeService.setTheme(options.theme); } - super(domElement, options, clipboardService, editorWorkerService, contextKeyService, instantiationService, codeEditorService, themeService, notificationService, contextMenuService); + super(domElement, options, clipboardService, editorWorkerService, contextKeyService, instantiationService, codeEditorService, themeService, notificationService, contextMenuService, editorProgressService); this._contextViewService = contextViewService; this._configurationService = configurationService; diff --git a/src/vs/editor/standalone/browser/standaloneCodeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneCodeServiceImpl.ts index f0605f829a..b1ad7b1e2f 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeServiceImpl.ts @@ -15,19 +15,19 @@ import { IResourceInput } from 'vs/platform/editor/common/editor'; export class StandaloneCodeEditorServiceImpl extends CodeEditorServiceImpl { - public getActiveCodeEditor(): ICodeEditor | undefined { - return undefined; // not supported in the standalone case + public getActiveCodeEditor(): ICodeEditor | null { + return null; // not supported in the standalone case } - public openCodeEditor(input: IResourceInput, source: ICodeEditor | undefined, sideBySide?: boolean): Promise { + public openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { if (!source) { - return Promise.resolve(undefined); + return Promise.resolve(null); } return Promise.resolve(this.doOpenEditor(source, input)); } - private doOpenEditor(editor: ICodeEditor, input: IResourceInput): ICodeEditor | undefined { + private doOpenEditor(editor: ICodeEditor, input: IResourceInput): ICodeEditor | null { const model = this.findModel(editor, input.resource); if (!model) { if (input.resource) { @@ -39,7 +39,7 @@ export class StandaloneCodeEditorServiceImpl extends CodeEditorServiceImpl { return editor; } } - return undefined; + return null; } const selection = (input.options ? input.options.selection : null); diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 72d640f10f..41faa97e62 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -38,6 +38,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { clearAllFontInfos } from 'vs/editor/browser/config/configuration'; +import { IEditorProgressService } from 'vs/platform/progress/common/progress'; type Omit = Pick>; @@ -120,6 +121,7 @@ export function createDiffEditor(domElement: HTMLElement, options?: IDiffEditorC services.get(INotificationService), services.get(IConfigurationService), services.get(IContextMenuService), + services.get(IEditorProgressService), null ); }); diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index 3cb5425139..a7dec000dd 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -1494,6 +1494,25 @@ suite('Editor Controller - Regression tests', () => { }); }); + test('issue #74722: Pasting whole line does not replace selection', () => { + usingCursor({ + text: [ + 'line1', + 'line sel 2', + 'line3' + ], + }, (model, cursor) => { + cursor.setSelections('test', [new Selection(2, 6, 2, 9)]); + + cursorCommand(cursor, H.Paste, { text: 'line1\n', pasteOnNewLine: true }); + + assert.equal(model.getLineContent(1), 'line1'); + assert.equal(model.getLineContent(2), 'line line1'); + assert.equal(model.getLineContent(3), ' 2'); + assert.equal(model.getLineContent(4), 'line3'); + }); + }); + test('issue #4996: Multiple cursor paste pastes contents of all cursors', () => { usingCursor({ text: [ @@ -2572,7 +2591,37 @@ suite('Editor Controller - Cursor Configuration', () => { '', ' }', ].join('\n')); - assertCursor(cursor, new Position(5, 1)); + assertCursor(cursor, new Position(4, 1)); + }); + + model.dispose(); + }); + + test('issue #40695: maintain cursor position when copying lines using ctrl+c, ctrl+v', () => { + let model = createTextModel( + [ + ' function f() {', + ' // I\'m gonna copy this line', + ' // Another line', + ' return 3;', + ' }', + ].join('\n') + ); + + withTestCodeEditor(null, { model: model }, (editor, cursor) => { + + editor.setSelections([new Selection(4, 10, 4, 10)]); + cursorCommand(cursor, H.Paste, { text: ' // I\'m gonna copy this line\n', pasteOnNewLine: true }); + + assert.equal(model.getValue(), [ + ' function f() {', + ' // I\'m gonna copy this line', + ' // Another line', + ' // I\'m gonna copy this line', + ' return 3;', + ' }', + ].join('\n')); + assertCursor(cursor, new Position(5, 10)); }); model.dispose(); @@ -4822,6 +4871,32 @@ suite('autoClosingPairs', () => { mode.dispose(); }); + test('issue #53357: Over typing ignores characters after backslash', () => { + let mode = new AutoClosingMode(); + usingCursor({ + text: [ + 'console.log();' + ], + languageIdentifier: mode.getLanguageIdentifier() + }, (model, cursor) => { + + cursor.setSelections('test', [new Selection(1, 13, 1, 13)]); + + cursorCommand(cursor, H.Type, { text: '\'' }, 'keyboard'); + assert.equal(model.getValue(), 'console.log(\'\');'); + + cursorCommand(cursor, H.Type, { text: 'it' }, 'keyboard'); + assert.equal(model.getValue(), 'console.log(\'it\');'); + + cursorCommand(cursor, H.Type, { text: '\\' }, 'keyboard'); + assert.equal(model.getValue(), 'console.log(\'it\\\');'); + + cursorCommand(cursor, H.Type, { text: '\'' }, 'keyboard'); + assert.equal(model.getValue(), 'console.log(\'it\\\'\'\');'); + }); + mode.dispose(); + }); + test('issue #2773: Accents (´`¨^, others?) are inserted in the wrong position (Mac)', () => { let mode = new AutoClosingMode(); usingCursor({ diff --git a/src/vs/editor/test/browser/controller/imeTester.ts b/src/vs/editor/test/browser/controller/imeTester.ts index cbf8b2f460..67d317cdc5 100644 --- a/src/vs/editor/test/browser/controller/imeTester.ts +++ b/src/vs/editor/test/browser/controller/imeTester.ts @@ -86,8 +86,14 @@ function doCreateTest(description: string, inputStr: string, expectedStr: string let model = new SingleLineTestModel('some text'); const textAreaInputHost: ITextAreaInputHost = { - getPlainTextToCopy: (): string => '', - getHTMLToCopy: (): string => '', + getDataToCopy: () => { + return { + isFromEmptySelection: false, + multicursorText: null, + text: '', + html: undefined + }; + }, getScreenReaderContent: (currentState: TextAreaState): TextAreaState => { if (browser.isIPad) { diff --git a/src/vs/editor/test/browser/editorTestServices.ts b/src/vs/editor/test/browser/editorTestServices.ts index 0d6ede3a87..a522e63b7e 100644 --- a/src/vs/editor/test/browser/editorTestServices.ts +++ b/src/vs/editor/test/browser/editorTestServices.ts @@ -14,10 +14,10 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti export class TestCodeEditorService extends AbstractCodeEditorService { public lastInput?: IResourceInput; - public getActiveCodeEditor(): ICodeEditor | undefined { return undefined; } - public openCodeEditor(input: IResourceInput, _source: ICodeEditor | undefined, _sideBySide?: boolean): Promise { + public getActiveCodeEditor(): ICodeEditor | null { return null; } + public openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { this.lastInput = input; - return Promise.resolve(undefined); + return Promise.resolve(null); } public registerDecorationType(key: string, options: IDecorationRenderOptions, parentTypeKey?: string): void { } public removeDecorationType(key: string): void { } diff --git a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts index c2147f5bec..40a1ca784f 100644 --- a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts +++ b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts @@ -15,12 +15,12 @@ import { TestTheme, TestThemeService } from 'vs/platform/theme/test/common/testT const themeServiceMock = new TestThemeService(); export class TestCodeEditorServiceImpl extends CodeEditorServiceImpl { - getActiveCodeEditor(): ICodeEditor | undefined { - return undefined; + getActiveCodeEditor(): ICodeEditor | null { + return null; } - openCodeEditor(input: IResourceInput, source: ICodeEditor | undefined, sideBySide?: boolean): Promise { - return Promise.resolve(undefined); + openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { + return Promise.resolve(null); } } diff --git a/src/vs/editor/test/browser/testCodeEditor.ts b/src/vs/editor/test/browser/testCodeEditor.ts index 1c7fb6bad4..61546080ed 100644 --- a/src/vs/editor/test/browser/testCodeEditor.ts +++ b/src/vs/editor/test/browser/testCodeEditor.ts @@ -42,9 +42,9 @@ export class TestCodeEditor extends CodeEditorWidget implements editorBrowser.IC public getCursor(): Cursor | undefined { return this._modelData ? this._modelData.cursor : undefined; } - public registerAndInstantiateContribution(ctor: any): T { + public registerAndInstantiateContribution(id: string, ctor: any): T { let r = this._instantiationService.createInstance(ctor, this); - this._contributions[r.getId()] = r; + this._contributions[id] = r; return r; } public dispose() { diff --git a/src/vs/editor/test/common/view/minimapCharRenderer.test.ts b/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts similarity index 70% rename from src/vs/editor/test/common/view/minimapCharRenderer.test.ts rename to src/vs/editor/test/browser/view/minimapCharRenderer.test.ts index 15a3c05e21..cba210f9c9 100644 --- a/src/vs/editor/test/common/view/minimapCharRenderer.test.ts +++ b/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts @@ -5,9 +5,8 @@ import * as assert from 'assert'; import { RGBA8 } from 'vs/editor/common/core/rgba'; -import { Constants } from 'vs/editor/common/view/minimapCharRenderer'; -import { getOrCreateMinimapCharRenderer } from 'vs/editor/common/view/runtimeMinimapCharRenderer'; -import { MinimapCharRendererFactory } from 'vs/editor/test/common/view/minimapCharRendererFactory'; +import { Constants } from 'vs/editor/browser/viewParts/minimap/minimapCharSheet'; +import { MinimapCharRendererFactory } from 'vs/editor/browser/viewParts/minimap/minimapCharRendererFactory'; suite('MinimapCharRenderer', () => { @@ -75,11 +74,11 @@ suite('MinimapCharRenderer', () => { test('letter d @ 2x', () => { setSampleData('d'.charCodeAt(0), sampleD); - let renderer = MinimapCharRendererFactory.create(sampleData!); + let renderer = MinimapCharRendererFactory.createFromSampleData(sampleData!, 2); let background = new RGBA8(0, 0, 0, 255); let color = new RGBA8(255, 255, 255, 255); - let imageData = createFakeImageData(Constants.x2_CHAR_WIDTH, Constants.x2_CHAR_HEIGHT); + let imageData = createFakeImageData(Constants.BASE_CHAR_WIDTH * 2, Constants.BASE_CHAR_HEIGHT * 2); // set the background color for (let i = 0, len = imageData.data.length / 4; i < len; i++) { imageData.data[4 * i + 0] = background.r; @@ -87,55 +86,28 @@ suite('MinimapCharRenderer', () => { imageData.data[4 * i + 2] = background.b; imageData.data[4 * i + 3] = 255; } - renderer.x2RenderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, false); + renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, false); let actual: number[] = []; for (let i = 0; i < imageData.data.length; i++) { actual[i] = imageData.data[i]; } + assert.deepEqual(actual, [ - 0x00, 0x00, 0x00, 0xFF, 0x6D, 0x6D, 0x6D, 0xFF, - 0xBB, 0xBB, 0xBB, 0xFF, 0xBE, 0xBE, 0xBE, 0xFF, - 0x94, 0x94, 0x94, 0xFF, 0x7E, 0x7E, 0x7E, 0xFF, - 0xB1, 0xB1, 0xB1, 0xFF, 0xBB, 0xBB, 0xBB, 0xFF, - ]); - }); - - test('letter d @ 2x at runtime', () => { - let renderer = getOrCreateMinimapCharRenderer(); - - let background = new RGBA8(0, 0, 0, 255); - let color = new RGBA8(255, 255, 255, 255); - let imageData = createFakeImageData(Constants.x2_CHAR_WIDTH, Constants.x2_CHAR_HEIGHT); - // set the background color - for (let i = 0, len = imageData.data.length / 4; i < len; i++) { - imageData.data[4 * i + 0] = background.r; - imageData.data[4 * i + 1] = background.g; - imageData.data[4 * i + 2] = background.b; - imageData.data[4 * i + 3] = 255; - } - - renderer.x2RenderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, false); - - let actual: number[] = []; - for (let i = 0; i < imageData.data.length; i++) { - actual[i] = imageData.data[i]; - } - assert.deepEqual(actual, [ - 0x00, 0x00, 0x00, 0xFF, 0x6D, 0x6D, 0x6D, 0xFF, - 0xBB, 0xBB, 0xBB, 0xFF, 0xBE, 0xBE, 0xBE, 0xFF, - 0x94, 0x94, 0x94, 0xFF, 0x7E, 0x7E, 0x7E, 0xFF, - 0xB1, 0xB1, 0xB1, 0xFF, 0xBB, 0xBB, 0xBB, 0xFF, + 0x2E, 0x2E, 0x2E, 0xFF, 0xAD, 0xAD, 0xAD, 0xFF, + 0xC6, 0xC6, 0xC6, 0xFF, 0xC8, 0xC8, 0xC8, 0xFF, + 0xC1, 0xC1, 0xC1, 0xFF, 0xCC, 0xCC, 0xCC, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, ]); }); test('letter d @ 1x', () => { setSampleData('d'.charCodeAt(0), sampleD); - let renderer = MinimapCharRendererFactory.create(sampleData!); + let renderer = MinimapCharRendererFactory.createFromSampleData(sampleData!, 1); let background = new RGBA8(0, 0, 0, 255); let color = new RGBA8(255, 255, 255, 255); - let imageData = createFakeImageData(Constants.x1_CHAR_WIDTH, Constants.x1_CHAR_HEIGHT); + let imageData = createFakeImageData(Constants.BASE_CHAR_WIDTH, Constants.BASE_CHAR_HEIGHT); // set the background color for (let i = 0, len = imageData.data.length / 4; i < len; i++) { imageData.data[4 * i + 0] = background.r; @@ -144,42 +116,17 @@ suite('MinimapCharRenderer', () => { imageData.data[4 * i + 3] = 255; } - renderer.x1RenderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, false); + renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, false); let actual: number[] = []; for (let i = 0; i < imageData.data.length; i++) { actual[i] = imageData.data[i]; } + assert.deepEqual(actual, [ - 0x55, 0x55, 0x55, 0xFF, - 0x93, 0x93, 0x93, 0xFF, + 0xCB, 0xCB, 0xCB, 0xFF, + 0x82, 0x82, 0x82, 0xFF, ]); }); - test('letter d @ 1x at runtime', () => { - let renderer = getOrCreateMinimapCharRenderer(); - - let background = new RGBA8(0, 0, 0, 255); - let color = new RGBA8(255, 255, 255, 255); - let imageData = createFakeImageData(Constants.x1_CHAR_WIDTH, Constants.x1_CHAR_HEIGHT); - // set the background color - for (let i = 0, len = imageData.data.length / 4; i < len; i++) { - imageData.data[4 * i + 0] = background.r; - imageData.data[4 * i + 1] = background.g; - imageData.data[4 * i + 2] = background.b; - imageData.data[4 * i + 3] = 255; - } - - renderer.x1RenderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, false); - - let actual: number[] = []; - for (let i = 0; i < imageData.data.length; i++) { - actual[i] = imageData.data[i]; - } - assert.deepEqual(actual, [ - 0x55, 0x55, 0x55, 0xFF, - 0x93, 0x93, 0x93, 0xFF, - ]); - }); - -}); \ No newline at end of file +}); diff --git a/src/vs/editor/test/browser/view/minimapFontCreator.ts b/src/vs/editor/test/browser/view/minimapFontCreator.ts index 23eb374835..b52adb977f 100644 --- a/src/vs/editor/test/browser/view/minimapFontCreator.ts +++ b/src/vs/editor/test/browser/view/minimapFontCreator.ts @@ -4,31 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import { RGBA8 } from 'vs/editor/common/core/rgba'; -import { Constants, MinimapCharRenderer } from 'vs/editor/common/view/minimapCharRenderer'; -import { getOrCreateMinimapCharRenderer } from 'vs/editor/common/view/runtimeMinimapCharRenderer'; -import { MinimapCharRendererFactory } from 'vs/editor/test/common/view/minimapCharRendererFactory'; +import { MinimapCharRenderer } from 'vs/editor/browser/viewParts/minimap/minimapCharRenderer'; +import { Constants } from 'vs/editor/browser/viewParts/minimap/minimapCharSheet'; +import { MinimapCharRendererFactory } from 'vs/editor/browser/viewParts/minimap/minimapCharRendererFactory'; -let canvas = document.getElementById('my-canvas'); -let ctx = canvas.getContext('2d')!; - -canvas.style.height = 100 + 'px'; -canvas.height = 100; - -canvas.width = Constants.CHAR_COUNT * Constants.SAMPLED_CHAR_WIDTH; -canvas.style.width = (Constants.CHAR_COUNT * Constants.SAMPLED_CHAR_WIDTH) + 'px'; - -ctx.fillStyle = '#ffffff'; -ctx.font = 'bold 16px monospace'; -for (let chCode = Constants.START_CH_CODE; chCode <= Constants.END_CH_CODE; chCode++) { - ctx.fillText(String.fromCharCode(chCode), (chCode - Constants.START_CH_CODE) * Constants.SAMPLED_CHAR_WIDTH, Constants.SAMPLED_CHAR_HEIGHT); -} - -let sampleData = ctx.getImageData(0, 4, Constants.SAMPLED_CHAR_WIDTH * Constants.CHAR_COUNT, Constants.SAMPLED_CHAR_HEIGHT); -let minimapCharRenderer = MinimapCharRendererFactory.create(sampleData.data); +let sampleData = MinimapCharRendererFactory.createSampleData('monospace'); +let minimapCharRenderer1x = MinimapCharRendererFactory.createFromSampleData(sampleData.data, 1); +let minimapCharRenderer2x = MinimapCharRendererFactory.createFromSampleData(sampleData.data, 2); +let minimapCharRenderer4x = MinimapCharRendererFactory.createFromSampleData(sampleData.data, 4); +let minimapCharRenderer6x = MinimapCharRendererFactory.createFromSampleData(sampleData.data, 6); renderImageData(sampleData, 10, 100); -renderMinimapCharRenderer(minimapCharRenderer, 400); -renderMinimapCharRenderer(getOrCreateMinimapCharRenderer(), 600); +renderMinimapCharRenderer(minimapCharRenderer1x, 400, 1); +renderMinimapCharRenderer(minimapCharRenderer2x, 500, 2); +renderMinimapCharRenderer(minimapCharRenderer4x, 600, 4); +renderMinimapCharRenderer(minimapCharRenderer6x, 750, 8); function createFakeImageData(width: number, height: number): ImageData { return { @@ -38,13 +28,15 @@ function createFakeImageData(width: number, height: number): ImageData { }; } -function renderMinimapCharRenderer(minimapCharRenderer: MinimapCharRenderer, y: number): void { - +function renderMinimapCharRenderer(minimapCharRenderer: MinimapCharRenderer, y: number, scale: number): void { let background = new RGBA8(0, 0, 0, 255); let color = new RGBA8(255, 255, 255, 255); { - let x2 = createFakeImageData(Constants.x2_CHAR_WIDTH * Constants.CHAR_COUNT, Constants.x2_CHAR_HEIGHT); + let x2 = createFakeImageData( + Constants.BASE_CHAR_WIDTH * scale * Constants.CHAR_COUNT, + Constants.BASE_CHAR_HEIGHT * scale + ); // set the background color for (let i = 0, len = x2.data.length / 4; i < len; i++) { x2.data[4 * i + 0] = background.r; @@ -54,67 +46,49 @@ function renderMinimapCharRenderer(minimapCharRenderer: MinimapCharRenderer, y: } let dx = 0; for (let chCode = Constants.START_CH_CODE; chCode <= Constants.END_CH_CODE; chCode++) { - minimapCharRenderer.x2RenderChar(x2, dx, 0, chCode, color, background, false); - dx += Constants.x2_CHAR_WIDTH; + minimapCharRenderer.renderChar(x2, dx, 0, chCode, color, background, false); + dx += Constants.BASE_CHAR_WIDTH * scale; } renderImageData(x2, 10, y); } - { - let x1 = createFakeImageData(Constants.x1_CHAR_WIDTH * Constants.CHAR_COUNT, Constants.x1_CHAR_HEIGHT); - // set the background color - for (let i = 0, len = x1.data.length / 4; i < len; i++) { - x1.data[4 * i + 0] = background.r; - x1.data[4 * i + 1] = background.g; - x1.data[4 * i + 2] = background.b; - x1.data[4 * i + 3] = 255; - } - let dx = 0; - for (let chCode = Constants.START_CH_CODE; chCode <= Constants.END_CH_CODE; chCode++) { - minimapCharRenderer.x1RenderChar(x1, dx, 0, chCode, color, background, false); - dx += Constants.x1_CHAR_WIDTH; - } - renderImageData(x1, 10, y + 100); - } } (function () { - let r = 'let x2Data = [', offset = 0; + let r = 'let x2Data = [', + offset = 0; for (let charIndex = 0; charIndex < Constants.CHAR_COUNT; charIndex++) { let charCode = charIndex + Constants.START_CH_CODE; r += '\n\n// ' + String.fromCharCode(charCode); - for (let i = 0; i < Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH; i++) { + for (let i = 0; i < Constants.BASE_CHAR_HEIGHT * 2; i++) { if (i % 2 === 0) { r += '\n'; } - r += minimapCharRenderer.x2charData[offset] + ','; + r += (minimapCharRenderer2x as any).charDataNormal[offset] + ','; offset++; } - } r += '\n\n]'; console.log(r); })(); (function () { - let r = 'let x1Data = [', offset = 0; + let r = 'let x1Data = [', + offset = 0; for (let charIndex = 0; charIndex < Constants.CHAR_COUNT; charIndex++) { let charCode = charIndex + Constants.START_CH_CODE; r += '\n\n// ' + String.fromCharCode(charCode); - for (let i = 0; i < Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH; i++) { + for (let i = 0; i < Constants.BASE_CHAR_HEIGHT * Constants.BASE_CHAR_WIDTH; i++) { r += '\n'; - r += minimapCharRenderer.x1charData[offset] + ','; + r += (minimapCharRenderer1x as any).charDataNormal[offset] + ','; offset++; } - } r += '\n\n]'; console.log(r); })(); - - function renderImageData(imageData: ImageData, left: number, top: number): void { let output = ''; let offset = 0; @@ -127,7 +101,8 @@ function renderImageData(imageData: ImageData, left: number, top: number): void let A = imageData.data[offset + 3]; offset += 4; - output += `
`; + output += `
`; } } @@ -135,8 +110,8 @@ function renderImageData(imageData: ImageData, left: number, top: number): void domNode.style.position = 'absolute'; domNode.style.top = top + 'px'; domNode.style.left = left + 'px'; - domNode.style.width = (imageData.width * PX_SIZE) + 'px'; - domNode.style.height = (imageData.height * PX_SIZE) + 'px'; + domNode.style.width = imageData.width * PX_SIZE + 'px'; + domNode.style.height = imageData.height * PX_SIZE + 'px'; domNode.style.border = '1px solid #ccc'; domNode.style.background = '#000000'; domNode.innerHTML = output; diff --git a/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts b/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts index 4e948e22fe..08afa4604f 100644 --- a/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts +++ b/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts @@ -173,4 +173,60 @@ suite('CursorMove', () => { testColumnFromVisibleColumn('📚az', 4, 3, 4); testColumnFromVisibleColumn('📚az', 4, 4, 5); }); -}); \ No newline at end of file + + test('toStatusbarColumn', () => { + + function t(text: string, tabSize: number, column: number, expected: number): void { + assert.equal(CursorColumns.toStatusbarColumn(text, column, tabSize), expected, `<>`); + } + + t(' spaces', 4, 1, 1); + t(' spaces', 4, 2, 2); + t(' spaces', 4, 3, 3); + t(' spaces', 4, 4, 4); + t(' spaces', 4, 5, 5); + t(' spaces', 4, 6, 6); + t(' spaces', 4, 7, 7); + t(' spaces', 4, 8, 8); + t(' spaces', 4, 9, 9); + t(' spaces', 4, 10, 10); + t(' spaces', 4, 11, 11); + + t('\ttab', 4, 1, 1); + t('\ttab', 4, 2, 5); + t('\ttab', 4, 3, 6); + t('\ttab', 4, 4, 7); + t('\ttab', 4, 5, 8); + + t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 1, 1); + t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 2, 2); + t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 3, 2); + t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 4, 3); + t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 5, 3); + t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 6, 4); + t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 7, 4); + t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 8, 5); + t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 9, 5); + t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 10, 6); + t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 11, 6); + t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 12, 7); + t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 13, 7); + t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 14, 8); + t('𐌀𐌁𐌂𐌃𐌄𐌅𐌆', 4, 15, 8); + + t('🎈🎈🎈🎈', 4, 1, 1); + t('🎈🎈🎈🎈', 4, 2, 2); + t('🎈🎈🎈🎈', 4, 3, 2); + t('🎈🎈🎈🎈', 4, 4, 3); + t('🎈🎈🎈🎈', 4, 5, 3); + t('🎈🎈🎈🎈', 4, 6, 4); + t('🎈🎈🎈🎈', 4, 7, 4); + t('🎈🎈🎈🎈', 4, 8, 5); + t('🎈🎈🎈🎈', 4, 9, 5); + + t('何何何何', 4, 1, 1); + t('何何何何', 4, 2, 2); + t('何何何何', 4, 3, 3); + t('何何何何', 4, 4, 4); + }); +}); diff --git a/src/vs/editor/test/common/core/range.test.ts b/src/vs/editor/test/common/core/range.test.ts index eb9c385c79..9c7734aff3 100644 --- a/src/vs/editor/test/common/core/range.test.ts +++ b/src/vs/editor/test/common/core/range.test.ts @@ -87,9 +87,9 @@ suite('Editor Core - Range', () => { b = new Range(1, 1, 1, 4); assert.ok(Range.compareRangesUsingEnds(a, b) > 0, 'a.start = b.start, a.end > b.end'); - a = new Range(1, 1, 5, 1); + a = new Range(1, 2, 5, 1); b = new Range(1, 1, 1, 4); - assert.ok(Range.compareRangesUsingEnds(a, b) > 0, 'a.start = b.start, a.end > b.end'); + assert.ok(Range.compareRangesUsingEnds(a, b) > 0, 'a.start > b.start, a.end > b.end'); }); test('containsPosition', () => { diff --git a/src/vs/editor/test/common/diff/diffComputer.test.ts b/src/vs/editor/test/common/diff/diffComputer.test.ts index 041b0ceec5..293baadcfa 100644 --- a/src/vs/editor/test/common/diff/diffComputer.test.ts +++ b/src/vs/editor/test/common/diff/diffComputer.test.ts @@ -55,9 +55,10 @@ function assertDiff(originalLines: string[], modifiedLines: string[], expectedCh shouldComputeCharChanges, shouldPostProcessCharChanges, shouldIgnoreTrimWhitespace, - shouldMakePrettyDiff: true + shouldMakePrettyDiff: true, + maxComputationTime: 0 }); - let changes = diffComputer.computeDiff(); + let changes = diffComputer.computeDiff().changes; let extracted: IChange[] = []; for (let i = 0; i < changes.length; i++) { diff --git a/src/vs/editor/test/common/mocks/testConfiguration.ts b/src/vs/editor/test/common/mocks/testConfiguration.ts index a32c6062b0..5d45f641ec 100644 --- a/src/vs/editor/test/common/mocks/testConfiguration.ts +++ b/src/vs/editor/test/common/mocks/testConfiguration.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CommonEditorConfiguration, IEnvConfiguration } from 'vs/editor/common/config/commonEditorConfig'; -import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { IEditorOptions, EditorFontLigatures } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; @@ -33,6 +33,7 @@ export class TestConfiguration extends CommonEditorConfiguration { fontFamily: 'mockFont', fontWeight: 'normal', fontSize: 14, + fontFeatureSettings: EditorFontLigatures.OFF, lineHeight: 19, letterSpacing: 1.5, isMonospace: true, diff --git a/src/vs/editor/test/common/model/textModelSearch.test.ts b/src/vs/editor/test/common/model/textModelSearch.test.ts index d21546e1fb..bfd0260195 100644 --- a/src/vs/editor/test/common/model/textModelSearch.test.ts +++ b/src/vs/editor/test/common/model/textModelSearch.test.ts @@ -721,6 +721,20 @@ suite('TextModelSearch', () => { ); }); + test('issue #65281. \w should match line break.', () => { + assertFindMatches( + [ + 'this/is{', + 'a test', + '}', + ].join('\n'), + 'this/\\w*[^}]*', true, false, null, + [ + [1, 1, 3, 1] + ] + ); + }); + test('Simple find using unicode escape sequences', () => { assertFindMatches( regularText.join('\n'), diff --git a/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/src/vs/editor/test/common/model/textModelWithTokens.test.ts index 0b7385be1b..ace28184bc 100644 --- a/src/vs/editor/test/common/model/textModelWithTokens.test.ts +++ b/src/vs/editor/test/common/model/textModelWithTokens.test.ts @@ -25,8 +25,8 @@ suite('TextModelWithTokens', () => { } return { range: a.range.toString(), - open: a.open, - close: a.close, + open: a.open[0], + close: a.close[0], isOpen: a.isOpen }; } @@ -57,8 +57,8 @@ suite('TextModelWithTokens', () => { let ch = lineText.charAt(charIndex); if (charIsBracket[ch]) { expectedBrackets.push({ - open: openForChar[ch], - close: closeForChar[ch], + open: [openForChar[ch]], + close: [closeForChar[ch]], isOpen: charIsOpenBracket[ch], range: new Range(lineIndex + 1, charIndex + 1, lineIndex + 1, charIndex + 2) }); @@ -145,18 +145,18 @@ suite('TextModelWithTokens', () => { }); }); +function assertIsNotBracket(model: TextModel, lineNumber: number, column: number) { + const match = model.matchBracket(new Position(lineNumber, column)); + assert.equal(match, null, 'is not matching brackets at ' + lineNumber + ', ' + column); +} + +function assertIsBracket(model: TextModel, testPosition: Position, expected: [Range, Range]): void { + const actual = model.matchBracket(testPosition); + assert.deepEqual(actual, expected, 'matches brackets at ' + testPosition); +} + suite('TextModelWithTokens - bracket matching', () => { - function isNotABracket(model: TextModel, lineNumber: number, column: number) { - let match = model.matchBracket(new Position(lineNumber, column)); - assert.equal(match, null, 'is not matching brackets at ' + lineNumber + ', ' + column); - } - - function isBracket2(model: TextModel, testPosition: Position, expected: [Range, Range]): void { - let actual = model.matchBracket(testPosition); - assert.deepEqual(actual, expected, 'matches brackets at ' + testPosition); - } - const languageIdentifier = new LanguageIdentifier('bracketMode1', LanguageId.PlainText); let registration: IDisposable; @@ -180,21 +180,21 @@ suite('TextModelWithTokens - bracket matching', () => { ')]}{[('; let model = TextModel.createFromString(text, undefined, languageIdentifier); - isNotABracket(model, 1, 1); - isNotABracket(model, 1, 2); - isNotABracket(model, 1, 3); - isBracket2(model, new Position(1, 4), [new Range(1, 4, 1, 5), new Range(2, 3, 2, 4)]); - isBracket2(model, new Position(1, 5), [new Range(1, 5, 1, 6), new Range(2, 2, 2, 3)]); - isBracket2(model, new Position(1, 6), [new Range(1, 6, 1, 7), new Range(2, 1, 2, 2)]); - isBracket2(model, new Position(1, 7), [new Range(1, 6, 1, 7), new Range(2, 1, 2, 2)]); + assertIsNotBracket(model, 1, 1); + assertIsNotBracket(model, 1, 2); + assertIsNotBracket(model, 1, 3); + assertIsBracket(model, new Position(1, 4), [new Range(1, 4, 1, 5), new Range(2, 3, 2, 4)]); + assertIsBracket(model, new Position(1, 5), [new Range(1, 5, 1, 6), new Range(2, 2, 2, 3)]); + assertIsBracket(model, new Position(1, 6), [new Range(1, 6, 1, 7), new Range(2, 1, 2, 2)]); + assertIsBracket(model, new Position(1, 7), [new Range(1, 6, 1, 7), new Range(2, 1, 2, 2)]); - isBracket2(model, new Position(2, 1), [new Range(2, 1, 2, 2), new Range(1, 6, 1, 7)]); - isBracket2(model, new Position(2, 2), [new Range(2, 2, 2, 3), new Range(1, 5, 1, 6)]); - isBracket2(model, new Position(2, 3), [new Range(2, 3, 2, 4), new Range(1, 4, 1, 5)]); - isBracket2(model, new Position(2, 4), [new Range(2, 3, 2, 4), new Range(1, 4, 1, 5)]); - isNotABracket(model, 2, 5); - isNotABracket(model, 2, 6); - isNotABracket(model, 2, 7); + assertIsBracket(model, new Position(2, 1), [new Range(2, 1, 2, 2), new Range(1, 6, 1, 7)]); + assertIsBracket(model, new Position(2, 2), [new Range(2, 2, 2, 3), new Range(1, 5, 1, 6)]); + assertIsBracket(model, new Position(2, 3), [new Range(2, 3, 2, 4), new Range(1, 4, 1, 5)]); + assertIsBracket(model, new Position(2, 4), [new Range(2, 3, 2, 4), new Range(1, 4, 1, 5)]); + assertIsNotBracket(model, 2, 5); + assertIsNotBracket(model, 2, 6); + assertIsNotBracket(model, 2, 7); model.dispose(); }); @@ -238,7 +238,7 @@ suite('TextModelWithTokens - bracket matching', () => { let isABracket: { [lineNumber: number]: { [col: number]: boolean; }; } = { 1: {}, 2: {}, 3: {}, 4: {}, 5: {} }; for (let i = 0, len = brackets.length; i < len; i++) { let [testPos, b1, b2] = brackets[i]; - isBracket2(model, testPos, [b1, b2]); + assertIsBracket(model, testPos, [b1, b2]); isABracket[testPos.lineNumber][testPos.column] = true; } @@ -246,7 +246,7 @@ suite('TextModelWithTokens - bracket matching', () => { let line = model.getLineContent(i); for (let j = 1, lenJ = line.length + 1; j <= lenJ; j++) { if (!isABracket[i].hasOwnProperty(j)) { - isNotABracket(model, i, j); + assertIsNotBracket(model, i, j); } } } @@ -255,6 +255,88 @@ suite('TextModelWithTokens - bracket matching', () => { }); }); +suite('TextModelWithTokens', () => { + + test('bracket matching 3', () => { + + const languageIdentifier = new LanguageIdentifier('bracketMode2', LanguageId.PlainText); + const registration = LanguageConfigurationRegistry.register(languageIdentifier, { + brackets: [ + ['if', 'end if'], + ['loop', 'end loop'], + ['begin', 'end'] + ], + }); + + const text = [ + 'begin', + ' loop', + ' if then', + ' end if;', + ' end loop;', + 'end;', + '', + 'begin', + ' loop', + ' if then', + ' end ifa;', + ' end loop;', + 'end;', + ].join('\n'); + + const model = TextModel.createFromString(text, undefined, languageIdentifier); + + // ... is not matched + assertIsNotBracket(model, 10, 9); + + // ... is matched + assertIsBracket(model, new Position(3, 9), [new Range(3, 9, 3, 11), new Range(4, 9, 4, 15)]); + assertIsBracket(model, new Position(4, 9), [new Range(4, 9, 4, 15), new Range(3, 9, 3, 11)]); + + // ... is matched + assertIsBracket(model, new Position(2, 5), [new Range(2, 5, 2, 9), new Range(5, 5, 5, 13)]); + assertIsBracket(model, new Position(5, 5), [new Range(5, 5, 5, 13), new Range(2, 5, 2, 9)]); + + // ... is matched + assertIsBracket(model, new Position(1, 1), [new Range(1, 1, 1, 6), new Range(6, 1, 6, 4)]); + assertIsBracket(model, new Position(6, 1), [new Range(6, 1, 6, 4), new Range(1, 1, 1, 6)]); + + model.dispose(); + registration.dispose(); + }); + + test('bracket matching 4', () => { + + const languageIdentifier = new LanguageIdentifier('bracketMode2', LanguageId.PlainText); + const registration = LanguageConfigurationRegistry.register(languageIdentifier, { + brackets: [ + ['recordbegin', 'endrecord'], + ['simplerecordbegin', 'endrecord'], + ], + }); + + const text = [ + 'recordbegin', + ' simplerecordbegin', + ' endrecord', + 'endrecord', + ].join('\n'); + + const model = TextModel.createFromString(text, undefined, languageIdentifier); + + // ... is matched + assertIsBracket(model, new Position(1, 1), [new Range(1, 1, 1, 12), new Range(4, 1, 4, 10)]); + assertIsBracket(model, new Position(4, 1), [new Range(4, 1, 4, 10), new Range(1, 1, 1, 12)]); + + // ... is matched + assertIsBracket(model, new Position(2, 3), [new Range(2, 3, 2, 20), new Range(3, 3, 3, 12)]); + assertIsBracket(model, new Position(3, 3), [new Range(3, 3, 3, 12), new Range(2, 3, 2, 20)]); + + model.dispose(); + registration.dispose(); + }); +}); + suite('TextModelWithTokens regression tests', () => { diff --git a/src/vs/editor/test/common/modes/supports/characterPair.test.ts b/src/vs/editor/test/common/modes/supports/characterPair.test.ts index 62af25ed30..530e716018 100644 --- a/src/vs/editor/test/common/modes/supports/characterPair.test.ts +++ b/src/vs/editor/test/common/modes/supports/characterPair.test.ts @@ -8,6 +8,7 @@ import { StandardTokenType } from 'vs/editor/common/modes'; import { CharacterPairSupport } from 'vs/editor/common/modes/supports/characterPair'; import { TokenText, createFakeScopedLineTokens } from 'vs/editor/test/common/modesTestUtils'; import { StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration'; +import { find } from 'vs/base/common/arrays'; suite('CharacterPairSupport', () => { @@ -53,13 +54,8 @@ suite('CharacterPairSupport', () => { assert.deepEqual(characaterPairSupport.getSurroundingPairs(), []); }); - function findAutoClosingPair(characterPairSupport: CharacterPairSupport, character: string): StandardAutoClosingPairConditional | null { - for (const autoClosingPair of characterPairSupport.getAutoClosingPairs()) { - if (autoClosingPair.open === character) { - return autoClosingPair; - } - } - return null; + function findAutoClosingPair(characterPairSupport: CharacterPairSupport, character: string): StandardAutoClosingPairConditional | undefined { + return find(characterPairSupport.getAutoClosingPairs(), autoClosingPair => autoClosingPair.open === character); } function testShouldAutoClose(characterPairSupport: CharacterPairSupport, line: TokenText[], character: string, column: number): boolean { diff --git a/src/vs/editor/test/common/modes/supports/richEditBrackets.test.ts b/src/vs/editor/test/common/modes/supports/richEditBrackets.test.ts index a663ce591b..9f6c5ad309 100644 --- a/src/vs/editor/test/common/modes/supports/richEditBrackets.test.ts +++ b/src/vs/editor/test/common/modes/supports/richEditBrackets.test.ts @@ -9,71 +9,71 @@ import { BracketsUtils } from 'vs/editor/common/modes/supports/richEditBrackets' suite('richEditBrackets', () => { - function findPrevBracketInToken(reversedBracketRegex: RegExp, lineText: string, currentTokenStart: number, currentTokenEnd: number): Range | null { - return BracketsUtils.findPrevBracketInToken(reversedBracketRegex, 1, lineText, currentTokenStart, currentTokenEnd); + function findPrevBracketInRange(reversedBracketRegex: RegExp, lineText: string, currentTokenStart: number, currentTokenEnd: number): Range | null { + return BracketsUtils.findPrevBracketInRange(reversedBracketRegex, 1, lineText, currentTokenStart, currentTokenEnd); } - function findNextBracketInToken(forwardBracketRegex: RegExp, lineText: string, currentTokenStart: number, currentTokenEnd: number): Range | null { - return BracketsUtils.findNextBracketInToken(forwardBracketRegex, 1, lineText, currentTokenStart, currentTokenEnd); + function findNextBracketInRange(forwardBracketRegex: RegExp, lineText: string, currentTokenStart: number, currentTokenEnd: number): Range | null { + return BracketsUtils.findNextBracketInRange(forwardBracketRegex, 1, lineText, currentTokenStart, currentTokenEnd); } test('findPrevBracketInToken one char 1', () => { - let result = findPrevBracketInToken(/(\{)|(\})/i, '{', 0, 1); + let result = findPrevBracketInRange(/(\{)|(\})/i, '{', 0, 1); assert.equal(result!.startColumn, 1); assert.equal(result!.endColumn, 2); }); test('findPrevBracketInToken one char 2', () => { - let result = findPrevBracketInToken(/(\{)|(\})/i, '{{', 0, 1); + let result = findPrevBracketInRange(/(\{)|(\})/i, '{{', 0, 1); assert.equal(result!.startColumn, 1); assert.equal(result!.endColumn, 2); }); test('findPrevBracketInToken one char 3', () => { - let result = findPrevBracketInToken(/(\{)|(\})/i, '{hello world!', 0, 13); + let result = findPrevBracketInRange(/(\{)|(\})/i, '{hello world!', 0, 13); assert.equal(result!.startColumn, 1); assert.equal(result!.endColumn, 2); }); test('findPrevBracketInToken more chars 1', () => { - let result = findPrevBracketInToken(/(olleh)/i, 'hello world!', 0, 12); + let result = findPrevBracketInRange(/(olleh)/i, 'hello world!', 0, 12); assert.equal(result!.startColumn, 1); assert.equal(result!.endColumn, 6); }); test('findPrevBracketInToken more chars 2', () => { - let result = findPrevBracketInToken(/(olleh)/i, 'hello world!', 0, 5); + let result = findPrevBracketInRange(/(olleh)/i, 'hello world!', 0, 5); assert.equal(result!.startColumn, 1); assert.equal(result!.endColumn, 6); }); test('findPrevBracketInToken more chars 3', () => { - let result = findPrevBracketInToken(/(olleh)/i, ' hello world!', 0, 6); + let result = findPrevBracketInRange(/(olleh)/i, ' hello world!', 0, 6); assert.equal(result!.startColumn, 2); assert.equal(result!.endColumn, 7); }); test('findNextBracketInToken one char', () => { - let result = findNextBracketInToken(/(\{)|(\})/i, '{', 0, 1); + let result = findNextBracketInRange(/(\{)|(\})/i, '{', 0, 1); assert.equal(result!.startColumn, 1); assert.equal(result!.endColumn, 2); }); test('findNextBracketInToken more chars', () => { - let result = findNextBracketInToken(/(world)/i, 'hello world!', 0, 12); + let result = findNextBracketInRange(/(world)/i, 'hello world!', 0, 12); assert.equal(result!.startColumn, 7); assert.equal(result!.endColumn, 12); }); test('findNextBracketInToken with emoty result', () => { - let result = findNextBracketInToken(/(\{)|(\})/i, '', 0, 0); + let result = findNextBracketInRange(/(\{)|(\})/i, '', 0, 0); assert.equal(result, null); }); test('issue #3894: [Handlebars] Curly braces edit issues', () => { - let result = findPrevBracketInToken(/(\-\-!<)|(>\-\-)|(\{\{)|(\}\})/i, '{{asd}}', 0, 2); + let result = findPrevBracketInRange(/(\-\-!<)|(>\-\-)|(\{\{)|(\}\})/i, '{{asd}}', 0, 2); assert.equal(result!.startColumn, 1); assert.equal(result!.endColumn, 3); }); -}); \ No newline at end of file +}); diff --git a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts index d008e006ea..5409458a6d 100644 --- a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts +++ b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts @@ -105,7 +105,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { const colorMap = [null!, '#000000', '#ffffff', '#ff0000', '#00ff00', '#0000ff']; assert.equal( - tokenizeLineToHTML(text, lineTokens, colorMap, 0, 17, 4), + tokenizeLineToHTML(text, lineTokens, colorMap, 0, 17, 4, true), [ '
', 'Ciao', @@ -118,7 +118,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ); assert.equal( - tokenizeLineToHTML(text, lineTokens, colorMap, 0, 12, 4), + tokenizeLineToHTML(text, lineTokens, colorMap, 0, 12, 4, true), [ '
', 'Ciao', @@ -131,7 +131,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ); assert.equal( - tokenizeLineToHTML(text, lineTokens, colorMap, 0, 11, 4), + tokenizeLineToHTML(text, lineTokens, colorMap, 0, 11, 4, true), [ '
', 'Ciao', @@ -143,7 +143,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ); assert.equal( - tokenizeLineToHTML(text, lineTokens, colorMap, 1, 11, 4), + tokenizeLineToHTML(text, lineTokens, colorMap, 1, 11, 4, true), [ '
', 'iao', @@ -155,7 +155,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ); assert.equal( - tokenizeLineToHTML(text, lineTokens, colorMap, 4, 11, 4), + tokenizeLineToHTML(text, lineTokens, colorMap, 4, 11, 4, true), [ '
', ' ', @@ -166,7 +166,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ); assert.equal( - tokenizeLineToHTML(text, lineTokens, colorMap, 5, 11, 4), + tokenizeLineToHTML(text, lineTokens, colorMap, 5, 11, 4, true), [ '
', 'hello', @@ -176,7 +176,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ); assert.equal( - tokenizeLineToHTML(text, lineTokens, colorMap, 5, 10, 4), + tokenizeLineToHTML(text, lineTokens, colorMap, 5, 10, 4, true), [ '
', 'hello', @@ -185,7 +185,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ); assert.equal( - tokenizeLineToHTML(text, lineTokens, colorMap, 6, 9, 4), + tokenizeLineToHTML(text, lineTokens, colorMap, 6, 9, 4, true), [ '
', 'ell', @@ -238,7 +238,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { const colorMap = [null!, '#000000', '#ffffff', '#ff0000', '#00ff00', '#0000ff']; assert.equal( - tokenizeLineToHTML(text, lineTokens, colorMap, 0, 21, 4), + tokenizeLineToHTML(text, lineTokens, colorMap, 0, 21, 4, true), [ '
', '  ', @@ -252,7 +252,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ); assert.equal( - tokenizeLineToHTML(text, lineTokens, colorMap, 0, 17, 4), + tokenizeLineToHTML(text, lineTokens, colorMap, 0, 17, 4, true), [ '
', '  ', @@ -266,7 +266,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ); assert.equal( - tokenizeLineToHTML(text, lineTokens, colorMap, 0, 3, 4), + tokenizeLineToHTML(text, lineTokens, colorMap, 0, 3, 4, true), [ '
', '  ', diff --git a/src/vs/editor/test/common/services/modelService.test.ts b/src/vs/editor/test/common/services/modelService.test.ts index fb9552df29..7d657c12c2 100644 --- a/src/vs/editor/test/common/services/modelService.test.ts +++ b/src/vs/editor/test/common/services/modelService.test.ts @@ -365,7 +365,7 @@ assertComputeEdits(file1, file2); } } -class TestTextResourcePropertiesService implements ITextResourcePropertiesService { +export class TestTextResourcePropertiesService implements ITextResourcePropertiesService { _serviceBrand: undefined; @@ -375,11 +375,9 @@ class TestTextResourcePropertiesService implements ITextResourcePropertiesServic } getEOL(resource: URI, language?: string): string { - const filesConfiguration = this.configurationService.getValue<{ eol: string }>('files', { overrideIdentifier: language, resource }); - if (filesConfiguration && filesConfiguration.eol) { - if (filesConfiguration.eol !== 'auto') { - return filesConfiguration.eol; - } + const eol = this.configurationService.getValue('files.eol', { overrideIdentifier: language, resource }); + if (eol && eol !== 'auto') { + return eol; } return (platform.isLinux || platform.isMacintosh) ? '\n' : '\r\n'; } diff --git a/src/vs/editor/test/common/view/minimapCharRendererFactory.ts b/src/vs/editor/test/common/view/minimapCharRendererFactory.ts deleted file mode 100644 index 455cd757db..0000000000 --- a/src/vs/editor/test/common/view/minimapCharRendererFactory.ts +++ /dev/null @@ -1,172 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Constants, MinimapCharRenderer } from 'vs/editor/common/view/minimapCharRenderer'; - -const enum InternalConstants { - CA_CHANNELS_CNT = 2, -} - -export class MinimapCharRendererFactory { - - public static create(source: Uint8ClampedArray): MinimapCharRenderer { - const expectedLength = (Constants.SAMPLED_CHAR_HEIGHT * Constants.SAMPLED_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT * Constants.CHAR_COUNT); - if (source.length !== expectedLength) { - throw new Error('Unexpected source in MinimapCharRenderer'); - } - - let x2CharData = this.toGrayscale(MinimapCharRendererFactory._downsample2x(source)); - let x1CharData = this.toGrayscale(MinimapCharRendererFactory._downsample1x(source)); - return new MinimapCharRenderer(x2CharData, x1CharData); - } - - private static toGrayscale(charData: Uint8ClampedArray): Uint8ClampedArray { - let newLength = charData.length / 2; - let result = new Uint8ClampedArray(newLength); - let sourceOffset = 0; - for (let i = 0; i < newLength; i++) { - let color = charData[sourceOffset]; - let alpha = charData[sourceOffset + 1]; - let newColor = Math.round((color * alpha) / 255); - result[i] = newColor; - sourceOffset += 2; - } - return result; - } - - private static _extractSampledChar(source: Uint8ClampedArray, charIndex: number, dest: Uint8ClampedArray) { - let destOffset = 0; - for (let i = 0; i < Constants.SAMPLED_CHAR_HEIGHT; i++) { - let sourceOffset = ( - Constants.SAMPLED_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT * Constants.CHAR_COUNT * i - + Constants.SAMPLED_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT * charIndex - ); - for (let j = 0; j < Constants.SAMPLED_CHAR_WIDTH; j++) { - for (let c = 0; c < Constants.RGBA_CHANNELS_CNT; c++) { - dest[destOffset] = source[sourceOffset]; - sourceOffset++; - destOffset++; - } - } - } - } - - private static _downsample2xChar(source: Uint8ClampedArray, dest: Uint8ClampedArray): void { - // chars are 2 x 4px (width x height) - const resultLen = Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH * InternalConstants.CA_CHANNELS_CNT; - const result = new Uint16Array(resultLen); - for (let i = 0; i < resultLen; i++) { - result[i] = 0; - } - - let inputOffset = 0, globalOutputOffset = 0; - for (let i = 0; i < Constants.SAMPLED_CHAR_HEIGHT; i++) { - - let outputOffset = globalOutputOffset; - - let color = 0; - let alpha = 0; - for (let j = 0; j < Constants.SAMPLED_HALF_CHAR_WIDTH; j++) { - color += source[inputOffset]; // R - alpha += source[inputOffset + 3]; // A - inputOffset += Constants.RGBA_CHANNELS_CNT; - } - result[outputOffset] += color; - result[outputOffset + 1] += alpha; - outputOffset += InternalConstants.CA_CHANNELS_CNT; - - color = 0; - alpha = 0; - for (let j = 0; j < Constants.SAMPLED_HALF_CHAR_WIDTH; j++) { - color += source[inputOffset]; // R - alpha += source[inputOffset + 3]; // A - inputOffset += Constants.RGBA_CHANNELS_CNT; - } - result[outputOffset] += color; - result[outputOffset + 1] += alpha; - outputOffset += InternalConstants.CA_CHANNELS_CNT; - - if (i === 2 || i === 5 || i === 8) { - globalOutputOffset = outputOffset; - } - } - - for (let i = 0; i < resultLen; i++) { - dest[i] = result[i] / 12; // 15 it should be - } - } - - private static _downsample2x(data: Uint8ClampedArray): Uint8ClampedArray { - const resultLen = Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH * InternalConstants.CA_CHANNELS_CNT * Constants.CHAR_COUNT; - const result = new Uint8ClampedArray(resultLen); - - const sampledChar = new Uint8ClampedArray(Constants.SAMPLED_CHAR_HEIGHT * Constants.SAMPLED_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT); - const downsampledChar = new Uint8ClampedArray(Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH * InternalConstants.CA_CHANNELS_CNT); - - for (let charIndex = 0; charIndex < Constants.CHAR_COUNT; charIndex++) { - this._extractSampledChar(data, charIndex, sampledChar); - this._downsample2xChar(sampledChar, downsampledChar); - let resultOffset = (Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH * InternalConstants.CA_CHANNELS_CNT * charIndex); - for (let i = 0; i < downsampledChar.length; i++) { - result[resultOffset + i] = downsampledChar[i]; - } - } - - return result; - } - - private static _downsample1xChar(source: Uint8ClampedArray, dest: Uint8ClampedArray): void { - // chars are 1 x 2px (width x height) - const resultLen = Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH * InternalConstants.CA_CHANNELS_CNT; - const result = new Uint16Array(resultLen); - for (let i = 0; i < resultLen; i++) { - result[i] = 0; - } - - let inputOffset = 0, globalOutputOffset = 0; - for (let i = 0; i < Constants.SAMPLED_CHAR_HEIGHT; i++) { - - let outputOffset = globalOutputOffset; - - let color = 0; - let alpha = 0; - for (let j = 0; j < Constants.SAMPLED_CHAR_WIDTH; j++) { - color += source[inputOffset]; // R - alpha += source[inputOffset + 3]; // A - inputOffset += Constants.RGBA_CHANNELS_CNT; - } - result[outputOffset] += color; - result[outputOffset + 1] += alpha; - outputOffset += InternalConstants.CA_CHANNELS_CNT; - - if (i === 5) { - globalOutputOffset = outputOffset; - } - } - - for (let i = 0; i < resultLen; i++) { - dest[i] = result[i] / 50; // 60 it should be - } - } - - private static _downsample1x(data: Uint8ClampedArray): Uint8ClampedArray { - const resultLen = Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH * InternalConstants.CA_CHANNELS_CNT * Constants.CHAR_COUNT; - const result = new Uint8ClampedArray(resultLen); - - const sampledChar = new Uint8ClampedArray(Constants.SAMPLED_CHAR_HEIGHT * Constants.SAMPLED_CHAR_WIDTH * Constants.RGBA_CHANNELS_CNT); - const downsampledChar = new Uint8ClampedArray(Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH * InternalConstants.CA_CHANNELS_CNT); - - for (let charIndex = 0; charIndex < Constants.CHAR_COUNT; charIndex++) { - this._extractSampledChar(data, charIndex, sampledChar); - this._downsample1xChar(sampledChar, downsampledChar); - let resultOffset = (Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH * InternalConstants.CA_CHANNELS_CNT * charIndex); - for (let i = 0; i < downsampledChar.length; i++) { - result[resultOffset + i] = downsampledChar[i]; - } - } - - return result; - } -} diff --git a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts index 9ced04b91a..4a7b9fdbc7 100644 --- a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts +++ b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts @@ -48,7 +48,8 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { side: input.minimapSide, renderCharacters: input.minimapRenderCharacters, maxColumn: input.minimapMaxColumn, - showSlider: 'mouseover' + showSlider: 'mouseover', + scale: 1, }; options._write(EditorOption.minimap, minimapOptions); const scrollbarOptions: InternalEditorScrollbarOptions = { @@ -704,7 +705,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { contentWidth: 901, contentHeight: 800, - renderMinimap: RenderMinimap.Small, + renderMinimap: RenderMinimap.Text, minimapLeft: 911, minimapWidth: 89, viewportColumn: 89, @@ -762,7 +763,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { contentWidth: 901, contentHeight: 800, - renderMinimap: RenderMinimap.Large, + renderMinimap: RenderMinimap.Text, minimapLeft: 911, minimapWidth: 89, viewportColumn: 89, @@ -820,7 +821,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { contentWidth: 943, contentHeight: 800, - renderMinimap: RenderMinimap.Large, + renderMinimap: RenderMinimap.Text, minimapLeft: 953, minimapWidth: 47, viewportColumn: 94, @@ -878,7 +879,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { contentWidth: 943, contentHeight: 800, - renderMinimap: RenderMinimap.Large, + renderMinimap: RenderMinimap.Text, minimapLeft: 0, minimapWidth: 47, viewportColumn: 94, @@ -936,7 +937,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { contentWidth: 1026, contentHeight: 422, - renderMinimap: RenderMinimap.Large, + renderMinimap: RenderMinimap.Text, minimapLeft: 1104, minimapWidth: 83, viewportColumn: 83, diff --git a/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts b/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts index 7de20db2b2..887f2e4833 100644 --- a/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts +++ b/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { toUint32Array } from 'vs/editor/common/core/uint'; +import { toUint32Array } from 'vs/base/common/uint'; import { PrefixSumComputer, PrefixSumIndexOfResult } from 'vs/editor/common/viewModel/prefixSumComputer'; suite('Editor ViewModel - PrefixSumComputer', () => { diff --git a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts index 66ad6fde93..5f5beed381 100644 --- a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts +++ b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts @@ -9,7 +9,7 @@ import { IViewLineTokens } from 'vs/editor/common/core/lineTokens'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { TokenizationResult2 } from 'vs/editor/common/core/token'; -import { toUint32Array } from 'vs/editor/common/core/uint'; +import { toUint32Array } from 'vs/base/common/uint'; import { EndOfLinePreference } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import * as modes from 'vs/editor/common/modes'; diff --git a/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts b/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts index 15539b4ada..3a879f6fb0 100644 --- a/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts +++ b/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts @@ -210,7 +210,7 @@ suite('ViewModel', () => { new Range(3, 2, 3, 2), ], true, - 'ine2' + ['ine2', 'line3'] ); }); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 051c6543fd..24a87d5874 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -42,9 +42,9 @@ declare namespace monaco { export class CancellationTokenSource { constructor(parent?: CancellationToken); - readonly token: CancellationToken; + get token(): CancellationToken; cancel(): void; - dispose(): void; + dispose(cancel?: boolean): void; } export interface CancellationToken { @@ -55,7 +55,6 @@ declare namespace monaco { */ readonly onCancellationRequested: IEvent; } - /** * Uniform Resource Identifier (Uri) http://tools.ietf.org/html/rfc3986. * This class is a simple parser which creates the basic component parts @@ -118,7 +117,7 @@ declare namespace monaco { * namely the server name, would be missing. Therefore `Uri#fsPath` exists - it's sugar to ease working * with URIs that represent files on disk (`file` scheme). */ - readonly fsPath: string; + get fsPath(): string; with(change: { scheme?: string; authority?: string | null; @@ -132,7 +131,7 @@ declare namespace monaco { * * @param value A string which represents an Uri (see `Uri#toString`). */ - static parse(value: string, _strict?: boolean): Uri; + static parse(value: string): Uri; /** * Creates a new Uri from a file system path, e.g. `c:\my\files`, * `/usr/home`, or `\\server\share\some\path`. @@ -379,8 +378,8 @@ declare namespace monaco { } export interface IMarkdownString { - value: string; - isTrusted?: boolean; + readonly value: string; + readonly isTrusted?: boolean; uris?: { [href: string]: UriComponents; }; @@ -727,10 +726,6 @@ declare namespace monaco { */ readonly positionColumn: number; constructor(selectionStartLineNumber: number, selectionStartColumn: number, positionLineNumber: number, positionColumn: number); - /** - * Clone this selection. - */ - clone(): Selection; /** * Transform to a human-readable representation. */ @@ -1556,6 +1551,11 @@ declare namespace monaco.editor { * @return The text length. */ getValueLengthInRange(range: IRange): number; + /** + * Get the character count of text in a certain range. + * @param range The range describing what text length to get. + */ + getCharacterCountInRange(range: IRange): number; /** * Get the number of lines in the model. */ @@ -2209,10 +2209,6 @@ declare namespace monaco.editor { * An editor contribution that gets created every time a new editor gets created and gets disposed when the editor gets disposed. */ export interface IEditorContribution { - /** - * Get a unique identifier for this contribution. - */ - getId(): string; /** * Dispose this contribution. */ @@ -2581,7 +2577,7 @@ declare namespace monaco.editor { * Enable font ligatures. * Defaults to false. */ - fontLigatures?: boolean; + fontLigatures?: boolean | string; /** * Disable the use of `will-change` for the editor margin and lines layers. * The usage of `will-change` acts as a hint for browsers to create an extra layer. @@ -2941,6 +2937,11 @@ declare namespace monaco.editor { * Defaults to true. */ renderSideBySide?: boolean; + /** + * Timeout in milliseconds after which diff computation is cancelled. + * Defaults to 5000. + */ + maxComputationTime?: number; /** * Compute the diff by ignoring leading/trailing whitespace * Defaults to true. @@ -3104,10 +3105,8 @@ declare namespace monaco.editor { export enum RenderMinimap { None = 0, - Small = 1, - Large = 2, - SmallBlocks = 3, - LargeBlocks = 4 + Text = 1, + Blocks = 2 } /** @@ -3242,6 +3241,10 @@ declare namespace monaco.editor { * Defaults to 120. */ maxColumn?: number; + /** + * Relative size of the font in the minimap. Defaults to 1. + */ + scale?: number; } export type EditorMinimapOptions = Readonly>; @@ -3369,6 +3372,10 @@ declare namespace monaco.editor { * Configuration options for editor suggest widget */ export interface ISuggestOptions { + /** + * Overwrite word ends on accept. Default to false. + */ + overwriteOnAccept?: boolean; /** * Enable graceful matching. Defaults to true. */ @@ -4128,6 +4135,7 @@ declare namespace monaco.editor { readonly fontFamily: string; readonly fontWeight: string; readonly fontSize: number; + readonly fontFeatureSettings: string; readonly lineHeight: number; readonly letterSpacing: number; } diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index b0dcc47a24..c7bbf4f26e 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -206,19 +206,20 @@ export class MenuEntryActionViewItem extends ActionViewItem { } updateLabel(): void { - if (this.options.label) { + if (this.options.label && this.label) { this.label.textContent = this._commandAction.label; } } updateTooltip(): void { - const element = this.label; - const keybinding = this._keybindingService.lookupKeybinding(this._commandAction.id); - const keybindingLabel = keybinding && keybinding.getLabel(); + if (this.label) { + const keybinding = this._keybindingService.lookupKeybinding(this._commandAction.id); + const keybindingLabel = keybinding && keybinding.getLabel(); - element.title = keybindingLabel - ? localize('titleAndKb', "{0} ({1})", this._commandAction.label, keybindingLabel) - : this._commandAction.label; + this.label.title = keybindingLabel + ? localize('titleAndKb', "{0} ({1})", this._commandAction.label, keybindingLabel) + : this._commandAction.label; + } } updateClass(): void { @@ -250,8 +251,14 @@ export class MenuEntryActionViewItem extends ActionViewItem { MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass); } - addClasses(this.label, 'icon', iconClass); - this._itemClassDispose.value = toDisposable(() => removeClasses(this.label, 'icon', iconClass)); + if (this.label) { + addClasses(this.label, 'icon', iconClass); + this._itemClassDispose.value = toDisposable(() => { + if (this.label) { + removeClasses(this.label, 'icon', iconClass); + } + }); + } } } } diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 4ab0a0a55a..3849589d38 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -292,13 +292,13 @@ export class MenuItemAction extends ExecuteCommandAction { export class SyncActionDescriptor { - private _descriptor: SyncDescriptor0; + private readonly _descriptor: SyncDescriptor0; - private _id: string; - private _label?: string; - private _keybindings: IKeybindings | undefined; - private _keybindingContext: ContextKeyExpr | undefined; - private _keybindingWeight: number | undefined; + private readonly _id: string; + private readonly _label?: string; + private readonly _keybindings: IKeybindings | undefined; + private readonly _keybindingContext: ContextKeyExpr | undefined; + private readonly _keybindingWeight: number | undefined; constructor(ctor: IConstructorSignature2, id: string, label: string | undefined, keybindings?: IKeybindings, keybindingContext?: ContextKeyExpr, keybindingWeight?: number diff --git a/src/vs/platform/backup/electron-main/backupMainService.ts b/src/vs/platform/backup/electron-main/backupMainService.ts index 62ab01884a..fc3a03f350 100644 --- a/src/vs/platform/backup/electron-main/backupMainService.ts +++ b/src/vs/platform/backup/electron-main/backupMainService.ts @@ -28,9 +28,9 @@ export class BackupMainService implements IBackupMainService { protected backupHome: string; protected workspacesJsonPath: string; - private rootWorkspaces: IWorkspaceBackupInfo[]; - private folderWorkspaces: URI[]; - private emptyWorkspaces: IEmptyWindowBackupInfo[]; + private rootWorkspaces: IWorkspaceBackupInfo[] = []; + private folderWorkspaces: URI[] = []; + private emptyWorkspaces: IEmptyWindowBackupInfo[] = []; constructor( @IEnvironmentService environmentService: IEnvironmentService, @@ -55,8 +55,6 @@ export class BackupMainService implements IBackupMainService { } else if (Array.isArray(backups.emptyWorkspaces)) { // read legacy entries this.emptyWorkspaces = await this.validateEmptyWorkspaces(backups.emptyWorkspaces.map(backupFolder => ({ backupFolder }))); - } else { - this.emptyWorkspaces = []; } // read workspace backups @@ -70,6 +68,7 @@ export class BackupMainService implements IBackupMainService { } catch (e) { // ignore URI parsing exceptions } + this.rootWorkspaces = await this.validateWorkspaces(rootWorkspaces); // read folder backups @@ -131,7 +130,7 @@ export class BackupMainService implements IBackupMainService { private getHotExitConfig(): string { const config = this.configurationService.getValue(); - return (config && config.files && config.files.hotExit) || HotExitConfiguration.ON_EXIT; + return config?.files?.hotExit || HotExitConfiguration.ON_EXIT; } getEmptyWindowBackupPaths(): IEmptyWindowBackupInfo[] { @@ -213,14 +212,11 @@ export class BackupMainService implements IBackupMainService { } } - registerEmptyWindowBackupSync(backupFolder?: string, remoteAuthority?: string): string { + registerEmptyWindowBackupSync(backupFolderCandidate?: string, remoteAuthority?: string): string { // Generate a new folder if this is a new empty workspace - if (!backupFolder) { - backupFolder = this.getRandomEmptyWindowId(); - } - - if (!this.emptyWorkspaces.some(window => !!window.backupFolder && isEqual(window.backupFolder, backupFolder!, !platform.isLinux))) { + const backupFolder = backupFolderCandidate || this.getRandomEmptyWindowId(); + if (!this.emptyWorkspaces.some(window => !!window.backupFolder && isEqual(window.backupFolder, backupFolder, !platform.isLinux))) { this.emptyWorkspaces.push({ backupFolder, remoteAuthority }); this.saveSync(); } diff --git a/src/vs/platform/commands/common/commands.ts b/src/vs/platform/commands/common/commands.ts index 465dba5e88..283e068d2c 100644 --- a/src/vs/platform/commands/common/commands.ts +++ b/src/vs/platform/commands/common/commands.ts @@ -99,7 +99,7 @@ export const CommandsRegistry: ICommandRegistry = new class implements ICommandR let ret = toDisposable(() => { removeFn(); const command = this._commands.get(id); - if (command && command.isEmpty()) { + if (command?.isEmpty()) { this._commands.delete(id); } }); diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index e60c4e8da1..a2046fff15 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -519,14 +519,14 @@ export class Configuration { return folderConsolidatedConfiguration; } - private getFolderConfigurationModelForResource(resource: URI | null | undefined, workspace: Workspace | undefined): ConfigurationModel | null { + private getFolderConfigurationModelForResource(resource: URI | null | undefined, workspace: Workspace | undefined): ConfigurationModel | undefined { if (workspace && resource) { const root = workspace.getFolder(resource); if (root) { - return types.withUndefinedAsNull(this._folderConfigurations.get(root.uri)); + return this._folderConfigurations.get(root.uri); } } - return null; + return undefined; } toData(): IConfigurationData { @@ -606,6 +606,7 @@ export class ConfigurationChangeEvent extends AbstractConfigurationChangeEvent i private _changedConfiguration: ConfigurationModel = new ConfigurationModel(), private _changedConfigurationByResource: ResourceMap = new ResourceMap()) { super(); + this._source = ConfigurationTarget.DEFAULT; } get changedConfiguration(): IConfigurationModel { @@ -664,13 +665,7 @@ export class ConfigurationChangeEvent extends AbstractConfigurationChangeEvent i configurationModelsToSearch.push(...this._changedConfigurationByResource.values()); } - for (const configuration of configurationModelsToSearch) { - if (this.doesConfigurationContains(configuration, config)) { - return true; - } - } - - return false; + return configurationModelsToSearch.some(configuration => this.doesConfigurationContains(configuration, config)); } private changeWithKeys(keys: string[], resource?: URI): void { diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 2c2db932aa..e97f6ad0cf 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -173,7 +173,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { this.editorConfigurationSchema = { properties: {}, patternProperties: {}, additionalProperties: false, errorMessage: 'Unknown editor configuration setting', allowTrailingCommas: true, allowComments: true }; this.configurationProperties = {}; this.excludedConfigurationProperties = {}; - this.computeOverridePropertyPattern(); + this.overridePropertyPattern = this.computeOverridePropertyPattern(); contributionRegistry.registerSchema(editorConfigurationSchemaId, this.editorConfigurationSchema); } @@ -413,7 +413,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { delete windowSettings.patternProperties[this.overridePropertyPattern]; delete resourceSettings.patternProperties[this.overridePropertyPattern]; - this.computeOverridePropertyPattern(); + this.overridePropertyPattern = this.computeOverridePropertyPattern(); allSettings.patternProperties[this.overridePropertyPattern] = patternProperties; applicationSettings.patternProperties[this.overridePropertyPattern] = patternProperties; @@ -440,8 +440,8 @@ class ConfigurationRegistry implements IConfigurationRegistry { } } - private computeOverridePropertyPattern(): void { - this.overridePropertyPattern = this.overrideIdentifiers.length ? OVERRIDE_PATTERN_WITH_SUBSTITUTION.replace('${0}', this.overrideIdentifiers.map(identifier => strings.createRegExp(identifier, false).source).join('|')) : OVERRIDE_PROPERTY; + private computeOverridePropertyPattern(): string { + return this.overrideIdentifiers.length ? OVERRIDE_PATTERN_WITH_SUBSTITUTION.replace('${0}', this.overrideIdentifiers.map(identifier => strings.createRegExp(identifier, false).source).join('|')) : OVERRIDE_PROPERTY; } } diff --git a/src/vs/platform/contextkey/browser/contextKeyService.ts b/src/vs/platform/contextkey/browser/contextKeyService.ts index 6c676e9c27..26b066efa3 100644 --- a/src/vs/platform/contextkey/browser/contextKeyService.ts +++ b/src/vs/platform/contextkey/browser/contextKeyService.ts @@ -87,7 +87,7 @@ class NullContext extends Context { class ConfigAwareContextValuesContainer extends Context { - private static _keyPrefix = 'config.'; + private static readonly _keyPrefix = 'config.'; private readonly _values = new Map(); private readonly _listener: IDisposable; diff --git a/src/vs/platform/debug/common/extensionHostDebug.ts b/src/vs/platform/debug/common/extensionHostDebug.ts index 2c7cb8c52b..c3b4cf58e0 100644 --- a/src/vs/platform/debug/common/extensionHostDebug.ts +++ b/src/vs/platform/debug/common/extensionHostDebug.ts @@ -6,7 +6,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; import { IRemoteConsoleLog } from 'vs/base/common/console'; -import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { IProcessEnvironment } from 'vs/base/common/platform'; export const IExtensionHostDebugService = createDecorator('extensionHostDebugService'); @@ -53,5 +52,5 @@ export interface IExtensionHostDebugService { terminateSession(sessionId: string, subId?: string): void; readonly onTerminateSession: Event; - openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise; + openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment): Promise; } diff --git a/src/vs/platform/debug/common/extensionHostDebugIpc.ts b/src/vs/platform/debug/common/extensionHostDebugIpc.ts index fe7c358e99..1da4777adc 100644 --- a/src/vs/platform/debug/common/extensionHostDebugIpc.ts +++ b/src/vs/platform/debug/common/extensionHostDebugIpc.ts @@ -8,7 +8,6 @@ import { IReloadSessionEvent, ICloseSessionEvent, IAttachSessionEvent, ILogToSes import { Event, Emitter } from 'vs/base/common/event'; import { IRemoteConsoleLog } from 'vs/base/common/console'; import { Disposable } from 'vs/base/common/lifecycle'; -import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { IProcessEnvironment } from 'vs/base/common/platform'; export class ExtensionHostDebugBroadcastChannel implements IServerChannel { @@ -102,7 +101,7 @@ export class ExtensionHostDebugChannelClient extends Disposable implements IExte return this.channel.listen('terminate'); } - openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { + openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment): Promise { // TODO@Isidor //return this.channel.call('openExtensionDevelopmentHostWindow', [args, env]); return Promise.resolve(); diff --git a/src/vs/platform/diagnostics/node/diagnosticsService.ts b/src/vs/platform/diagnostics/node/diagnosticsService.ts index 7e005946a9..19f00a3fce 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsService.ts @@ -551,32 +551,32 @@ export class DiagnosticsService implements IDiagnosticsService { }); type WorkspaceStatsFileClassification = { rendererSessionId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - name: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + type: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; count: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; }; type WorkspaceStatsFileEvent = { rendererSessionId: string; - name: string; + type: string; count: number; }; stats.fileTypes.forEach(e => { this.telemetryService.publicLog2('workspace.stats.file', { rendererSessionId: workspace.rendererSessionId, - name: e.name, + type: e.name, count: e.count }); }); stats.launchConfigFiles.forEach(e => { this.telemetryService.publicLog2('workspace.stats.launchConfigFile', { rendererSessionId: workspace.rendererSessionId, - name: e.name, + type: e.name, count: e.count }); }); stats.configFiles.forEach(e => { this.telemetryService.publicLog2('workspace.stats.configFiles', { rendererSessionId: workspace.rendererSessionId, - name: e.name, + type: e.name, count: e.count }); }); diff --git a/src/vs/platform/dialogs/electron-main/dialogs.ts b/src/vs/platform/dialogs/electron-main/dialogs.ts index 9804e7067b..1cf45f39a7 100644 --- a/src/vs/platform/dialogs/electron-main/dialogs.ts +++ b/src/vs/platform/dialogs/electron-main/dialogs.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { MessageBoxOptions, SaveDialogOptions, OpenDialogOptions, dialog, FileFilter, BrowserWindow } from 'electron'; +import { MessageBoxOptions, MessageBoxReturnValue, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, dialog, FileFilter, BrowserWindow } from 'electron'; import { Queue } from 'vs/base/common/async'; import { IStateService } from 'vs/platform/state/node/state'; import { isMacintosh } from 'vs/base/common/platform'; import { dirname } from 'vs/base/common/path'; import { normalizeNFC } from 'vs/base/common/normalization'; import { exists } from 'vs/base/node/pfs'; -import { INativeOpenDialogOptions, MessageBoxReturnValue, SaveDialogReturnValue, OpenDialogReturnValue } from 'vs/platform/dialogs/node/dialogs'; +import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { withNullAsUndefined } from 'vs/base/common/types'; import { localize } from 'vs/nls'; import { WORKSPACE_FILTER } from 'vs/platform/workspaces/common/workspaces'; @@ -139,13 +139,11 @@ export class DialogMainService implements IDialogMainService { showMessageBox(options: MessageBoxOptions, window?: BrowserWindow): Promise { return this.getDialogQueue(window).queue(async () => { - return new Promise(resolve => { - if (window) { - return dialog.showMessageBox(window, options, (response, checkboxChecked) => resolve({ response, checkboxChecked })); - } + if (window) { + return dialog.showMessageBox(window, options); + } - return dialog.showMessageBox(options); - }); + return dialog.showMessageBox(options); }); } @@ -160,17 +158,16 @@ export class DialogMainService implements IDialogMainService { } return this.getDialogQueue(window).queue(async () => { - return new Promise(resolve => { - if (window) { - dialog.showSaveDialog(window, options, filePath => resolve({ filePath })); - } else { - dialog.showSaveDialog(options, filePath => resolve({ filePath })); - } - }).then(result => { - result.filePath = normalizePath(result.filePath); + let result: SaveDialogReturnValue; + if (window) { + result = await dialog.showSaveDialog(window, options); + } else { + result = await dialog.showSaveDialog(options); + } - return result; - }); + result.filePath = normalizePath(result.filePath); + + return result; }); } @@ -195,17 +192,16 @@ export class DialogMainService implements IDialogMainService { } // Show dialog - return new Promise(resolve => { - if (window) { - dialog.showOpenDialog(window, options, filePaths => resolve({ filePaths })); - } else { - dialog.showOpenDialog(options, filePaths => resolve({ filePaths })); - } - }).then(result => { - result.filePaths = normalizePaths(result.filePaths); + let result: OpenDialogReturnValue; + if (window) { + result = await dialog.showOpenDialog(window, options); + } else { + result = await dialog.showOpenDialog(options); + } - return result; - }); + result.filePaths = normalizePaths(result.filePaths); + + return result; }); } } diff --git a/src/vs/platform/dialogs/node/dialogs.ts b/src/vs/platform/dialogs/node/dialogs.ts index 37389a681e..13a3ff5696 100644 --- a/src/vs/platform/dialogs/node/dialogs.ts +++ b/src/vs/platform/dialogs/node/dialogs.ts @@ -13,16 +13,3 @@ export interface INativeOpenDialogOptions { telemetryEventName?: string; telemetryExtraData?: ITelemetryData; } - -export interface MessageBoxReturnValue { - response: number; - checkboxChecked: boolean; -} - -export interface SaveDialogReturnValue { - filePath?: string; -} - -export interface OpenDialogReturnValue { - filePaths?: string[]; -} diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index 86523369c3..85c811d7f5 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -19,6 +19,8 @@ import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { timeout } from 'vs/base/common/async'; import { IDriver, IElement, IWindowDriver } from 'vs/platform/driver/common/driver'; import { NativeImage } from 'electron'; +import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; +import { IElectronMainService } from 'vs/platform/electron/electron-main/electronMainService'; function isSilentKeyCode(keyCode: KeyCode) { return keyCode < KeyCode.KEY_0; @@ -35,7 +37,9 @@ export class Driver implements IDriver, IWindowDriverRegistry { constructor( private windowServer: IPCServer, private options: IDriverOptions, - @IWindowsMainService private readonly windowsMainService: IWindowsMainService + @IWindowsMainService private readonly windowsMainService: IWindowsMainService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, + @IElectronMainService private readonly electronMainService: any // {{SQL CARBON EDIT}} remove interface, naster work around ) { } async registerWindowDriver(windowId: number): Promise { @@ -75,11 +79,11 @@ export class Driver implements IDriver, IWindowDriverRegistry { throw new Error('Invalid window'); } this.reloadingWindowIds.add(windowId); - this.windowsMainService.reload(window); + this.lifecycleMainService.reload(window); } async exitApplication(): Promise { - return this.windowsMainService.quit(); + return this.electronMainService.quit(undefined); } async dispatchKeybinding(windowId: number, keybinding: string): Promise { diff --git a/src/vs/platform/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts index da2c36ad32..b2b56fd823 100644 --- a/src/vs/platform/editor/common/editor.ts +++ b/src/vs/platform/editor/common/editor.ts @@ -106,6 +106,21 @@ export enum EditorActivation { PRESERVE } +export enum EditorOpenContext { + + /** + * Default: the editor is opening via a programmatic call + * to the editor service API. + */ + API, + + /** + * Indicates that a user action triggered the opening, e.g. + * via mouse or keyboard use. + */ + USER +} + export interface IEditorOptions { /** @@ -179,6 +194,18 @@ export interface IEditorOptions { * Does not use editor overrides while opening the editor */ readonly ignoreOverrides?: boolean; + + /** + * A optional hint to signal in which context the editor opens. + * + * If configured to be `EditorOpenContext.USER`, this hint can be + * used in various places to control the experience. For example, + * if the editor to open fails with an error, a notification could + * inform about this in a modal dialog. If the editor opened through + * some background task, the notification would show in the background, + * not as a modal dialog. + */ + readonly context?: EditorOpenContext; } export interface ITextEditorSelection { diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index d01fcecdc7..fe42cd5672 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -4,20 +4,29 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; -import { MessageBoxOptions, shell, OpenDevToolsOptions, SaveDialogOptions, OpenDialogOptions, CrashReporterStartOptions, crashReporter, Menu, BrowserWindow, app } from 'electron'; +import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; +import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, CrashReporterStartOptions, crashReporter, Menu, BrowserWindow, app } from 'electron'; import { INativeOpenWindowOptions } from 'vs/platform/windows/node/window'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IOpenedWindow, OpenContext, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; -import { INativeOpenDialogOptions, MessageBoxReturnValue, SaveDialogReturnValue, OpenDialogReturnValue } from 'vs/platform/dialogs/node/dialogs'; +import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; -import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { AddFirstParameterToFunctions } from 'vs/base/common/types'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; +import { dirExists } from 'vs/base/node/pfs'; +import { URI } from 'vs/base/common/uri'; +import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; +import { ITelemetryData, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -export class ElectronMainService implements AddFirstParameterToFunctions /* only methods, not events */, number /* window ID */> { +export interface IElectronMainService extends AddFirstParameterToFunctions /* only methods, not events */, number | undefined /* window ID */> { } + +export const IElectronMainService = createDecorator('electronMainService'); + +export class ElectronMainService implements IElectronMainService { _serviceBrand: undefined; @@ -25,7 +34,8 @@ export class ElectronMainService implements AddFirstParameterToFunctions { + async getWindowCount(windowId: number | undefined): Promise { return this.windowsMainService.getWindowCount(); } - async getActiveWindowId(windowId: number): Promise { + async getActiveWindowId(windowId: number | undefined): Promise { const activeWindow = BrowserWindow.getFocusedWindow() || this.windowsMainService.getLastActiveWindow(); if (activeWindow) { return activeWindow.id; @@ -71,9 +81,9 @@ export class ElectronMainService implements AddFirstParameterToFunctions; - openWindow(windowId: number, toOpen: IWindowOpenable[], options?: INativeOpenWindowOptions): Promise; - openWindow(windowId: number, arg1?: IOpenEmptyWindowOptions | IWindowOpenable[], arg2?: INativeOpenWindowOptions): Promise { + openWindow(windowId: number | undefined, options?: IOpenEmptyWindowOptions): Promise; + openWindow(windowId: number | undefined, toOpen: IWindowOpenable[], options?: INativeOpenWindowOptions): Promise; + openWindow(windowId: number | undefined, arg1?: IOpenEmptyWindowOptions | IWindowOpenable[], arg2?: INativeOpenWindowOptions): Promise { if (Array.isArray(arg1)) { return this.doOpenWindow(windowId, arg1, arg2); } @@ -81,7 +91,7 @@ export class ElectronMainService implements AddFirstParameterToFunctions { + private async doOpenWindow(windowId: number | undefined, toOpen: IWindowOpenable[], options: INativeOpenWindowOptions = Object.create(null)): Promise { if (toOpen.length > 0) { this.windowsMainService.open({ context: OpenContext.API, @@ -99,26 +109,26 @@ export class ElectronMainService implements AddFirstParameterToFunctions { + private async doOpenEmptyWindow(windowId: number | undefined, options?: IOpenEmptyWindowOptions): Promise { this.windowsMainService.openEmptyWindow(OpenContext.API, options); } - async toggleFullScreen(windowId: number): Promise { - const window = this.windowsMainService.getWindowById(windowId); + async toggleFullScreen(windowId: number | undefined): Promise { + const window = this.windowById(windowId); if (window) { window.toggleFullScreen(); } } - async handleTitleDoubleClick(windowId: number): Promise { - const window = this.windowsMainService.getWindowById(windowId); + async handleTitleDoubleClick(windowId: number | undefined): Promise { + const window = this.windowById(windowId); if (window) { window.handleTitleDoubleClick(); } } - async isMaximized(windowId: number): Promise { - const window = this.windowsMainService.getWindowById(windowId); + async isMaximized(windowId: number | undefined): Promise { + const window = this.windowById(windowId); if (window) { return window.win.isMaximized(); } @@ -126,29 +136,29 @@ export class ElectronMainService implements AddFirstParameterToFunctions { - const window = this.windowsMainService.getWindowById(windowId); + async maximizeWindow(windowId: number | undefined): Promise { + const window = this.windowById(windowId); if (window) { window.win.maximize(); } } - async unmaximizeWindow(windowId: number): Promise { - const window = this.windowsMainService.getWindowById(windowId); + async unmaximizeWindow(windowId: number | undefined): Promise { + const window = this.windowById(windowId); if (window) { window.win.unmaximize(); } } - async minimizeWindow(windowId: number): Promise { - const window = this.windowsMainService.getWindowById(windowId); + async minimizeWindow(windowId: number | undefined): Promise { + const window = this.windowById(windowId); if (window) { window.win.minimize(); } } - async isWindowFocused(windowId: number): Promise { - const window = this.windowsMainService.getWindowById(windowId); + async isWindowFocused(windowId: number | undefined): Promise { + const window = this.windowById(windowId); if (window) { return window.win.isFocused(); } @@ -156,12 +166,12 @@ export class ElectronMainService implements AddFirstParameterToFunctions { + async focusWindow(windowId: number | undefined, options?: { windowId?: number; }): Promise { if (options && typeof options.windowId === 'number') { windowId = options.windowId; } - const window = this.windowsMainService.getWindowById(windowId); + const window = this.windowById(windowId); if (window) { if (isMacintosh) { window.win.show(); @@ -175,20 +185,20 @@ export class ElectronMainService implements AddFirstParameterToFunctions { + async showMessageBox(windowId: number | undefined, options: MessageBoxOptions): Promise { return this.dialogMainService.showMessageBox(options, this.toBrowserWindow(windowId)); } - async showSaveDialog(windowId: number, options: SaveDialogOptions): Promise { + async showSaveDialog(windowId: number | undefined, options: SaveDialogOptions): Promise { return this.dialogMainService.showSaveDialog(options, this.toBrowserWindow(windowId)); } - async showOpenDialog(windowId: number, options: OpenDialogOptions): Promise { + async showOpenDialog(windowId: number | undefined, options: OpenDialogOptions): Promise { return this.dialogMainService.showOpenDialog(options, this.toBrowserWindow(windowId)); } - private toBrowserWindow(windowId: number): BrowserWindow | undefined { - const window = this.windowsMainService.getWindowById(windowId); + private toBrowserWindow(windowId: number | undefined): BrowserWindow | undefined { + const window = this.windowById(windowId); if (window) { return window.win; } @@ -196,52 +206,90 @@ export class ElectronMainService implements AddFirstParameterToFunctions { - return this.windowsMainService.pickFileFolderAndOpen(options, this.windowsMainService.getWindowById(windowId)); + async pickFileFolderAndOpen(windowId: number | undefined, options: INativeOpenDialogOptions): Promise { + const paths = await this.dialogMainService.pickFileFolder(options); + if (paths) { + this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFileFolder', options.telemetryExtraData); + this.doOpenPicked(await Promise.all(paths.map(async path => (await dirExists(path)) ? { folderUri: URI.file(path) } : { fileUri: URI.file(path) })), options, windowId); + } } - async pickFileAndOpen(windowId: number, options: INativeOpenDialogOptions): Promise { - return this.windowsMainService.pickFileAndOpen(options, this.windowsMainService.getWindowById(windowId)); + async pickFolderAndOpen(windowId: number | undefined, options: INativeOpenDialogOptions): Promise { + const paths = await this.dialogMainService.pickFolder(options); + if (paths) { + this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFolder', options.telemetryExtraData); + this.doOpenPicked(paths.map(path => ({ folderUri: URI.file(path) })), options, windowId); + } } - async pickFolderAndOpen(windowId: number, options: INativeOpenDialogOptions): Promise { - return this.windowsMainService.pickFolderAndOpen(options, this.windowsMainService.getWindowById(windowId)); + async pickFileAndOpen(windowId: number | undefined, options: INativeOpenDialogOptions): Promise { + const paths = await this.dialogMainService.pickFile(options); + if (paths) { + this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFile', options.telemetryExtraData); + this.doOpenPicked(paths.map(path => ({ fileUri: URI.file(path) })), options, windowId); + } } - async pickWorkspaceAndOpen(windowId: number, options: INativeOpenDialogOptions): Promise { - return this.windowsMainService.pickWorkspaceAndOpen(options, this.windowsMainService.getWindowById(windowId)); + async pickWorkspaceAndOpen(windowId: number | undefined, options: INativeOpenDialogOptions): Promise { + const paths = await this.dialogMainService.pickWorkspace(options); + if (paths) { + this.sendPickerTelemetry(paths, options.telemetryEventName || 'openWorkspace', options.telemetryExtraData); + this.doOpenPicked(paths.map(path => ({ workspaceUri: URI.file(path) })), options, windowId); + } + } + + private doOpenPicked(openable: IWindowOpenable[], options: INativeOpenDialogOptions, windowId: number | undefined): void { + this.windowsMainService.open({ + context: OpenContext.DIALOG, + contextWindowId: windowId, + cli: this.environmentService.args, + urisToOpen: openable, + forceNewWindow: options.forceNewWindow + }); + } + + private sendPickerTelemetry(paths: string[], telemetryEventName: string, telemetryExtraData?: ITelemetryData) { + const numberOfPaths = paths ? paths.length : 0; + + // Telemetry + // __GDPR__TODO__ Dynamic event names and dynamic properties. Can not be registered statically. + this.telemetryService.publicLog(telemetryEventName, { + ...telemetryExtraData, + outcome: numberOfPaths ? 'success' : 'canceled', + numberOfPaths + }); } //#endregion //#region OS - async showItemInFolder(windowId: number, path: string): Promise { + async showItemInFolder(windowId: number | undefined, path: string): Promise { shell.showItemInFolder(path); } - async setRepresentedFilename(windowId: number, path: string): Promise { - const window = this.windowsMainService.getWindowById(windowId); + async setRepresentedFilename(windowId: number | undefined, path: string): Promise { + const window = this.windowById(windowId); if (window) { window.setRepresentedFilename(path); } } - async setDocumentEdited(windowId: number, edited: boolean): Promise { - const window = this.windowsMainService.getWindowById(windowId); + async setDocumentEdited(windowId: number | undefined, edited: boolean): Promise { + const window = this.windowById(windowId); if (window) { window.win.setDocumentEdited(edited); } } - async openExternal(windowId: number, url: string): Promise { + async openExternal(windowId: number | undefined, url: string): Promise { shell.openExternal(url); return true; } - async updateTouchBar(windowId: number, items: ISerializableCommandAction[][]): Promise { - const window = this.windowsMainService.getWindowById(windowId); + async updateTouchBar(windowId: number | undefined, items: ISerializableCommandAction[][]): Promise { + const window = this.windowById(windowId); if (window) { window.updateTouchBar(items); } @@ -279,44 +327,51 @@ export class ElectronMainService implements AddFirstParameterToFunctions { + async relaunch(windowId: number | undefined, options?: { addArgs?: string[], removeArgs?: string[] }): Promise { return this.lifecycleMainService.relaunch(options); } - async reload(windowId: number): Promise { - const window = this.windowsMainService.getWindowById(windowId); + async reload(windowId: number | undefined, options?: { disableExtensions?: boolean }): Promise { + const window = this.windowById(windowId); if (window) { - return this.windowsMainService.reload(window); + return this.lifecycleMainService.reload(window, options?.disableExtensions ? { _: [], 'disable-extensions': true } : undefined); } } - async closeWorkspace(windowId: number): Promise { - const window = this.windowsMainService.getWindowById(windowId); - if (window) { - return this.windowsMainService.closeWorkspace(window); - } - } - - async closeWindow(windowId: number): Promise { - const window = this.windowsMainService.getWindowById(windowId); + async closeWindow(windowId: number | undefined): Promise { + const window = this.windowById(windowId); if (window) { return window.win.close(); } } - async quit(windowId: number): Promise { - return this.windowsMainService.quit(); + async quit(windowId: number | undefined): Promise { + + // If the user selected to exit from an extension development host window, do not quit, but just + // close the window unless this is the last window that is opened. + const window = this.windowsMainService.getLastActiveWindow(); + if (window?.isExtensionDevelopmentHost && this.windowsMainService.getWindowCount() > 1) { + window.win.close(); + } + + // Otherwise: normal quit + else { + setTimeout(() => { + this.lifecycleMainService.quit(); + }, 10 /* delay to unwind callback stack (IPC) */); + } } //#endregion //#region Connectivity - async resolveProxy(windowId: number, url: string): Promise { + async resolveProxy(windowId: number | undefined, url: string): Promise { return new Promise(resolve => { - const window = this.windowsMainService.getWindowById(windowId); - if (window && window.win && window.win.webContents && window.win.webContents.session) { - window.win.webContents.session.resolveProxy(url, proxy => resolve(proxy)); + const window = this.windowById(windowId); + const session = window?.win?.webContents?.session; + if (session) { + session.resolveProxy(url, proxy => resolve(proxy)); } else { resolve(); } @@ -327,18 +382,18 @@ export class ElectronMainService implements AddFirstParameterToFunctions { - const window = this.windowsMainService.getWindowById(windowId); + async openDevTools(windowId: number | undefined, options?: OpenDevToolsOptions): Promise { + const window = this.windowById(windowId); if (window) { window.win.webContents.openDevTools(options); } } - async toggleDevTools(windowId: number): Promise { - const window = this.windowsMainService.getWindowById(windowId); + async toggleDevTools(windowId: number | undefined): Promise { + const window = this.windowById(windowId); if (window) { const contents = window.win.webContents; - if (isMacintosh && window.hasHiddenTitleBarStyle() && !window.isFullScreen() && !contents.isDevToolsOpened()) { + if (isMacintosh && window.hasHiddenTitleBarStyle && !window.isFullScreen && !contents.isDevToolsOpened()) { contents.openDevTools({ mode: 'undocked' }); // due to https://github.com/electron/electron/issues/3647 } else { contents.toggleDevTools(); @@ -346,7 +401,7 @@ export class ElectronMainService implements AddFirstParameterToFunctions { + async startCrashReporter(windowId: number | undefined, options: CrashReporterStartOptions): Promise { crashReporter.start(options); } @@ -356,16 +411,25 @@ export class ElectronMainService implements AddFirstParameterToFunctions { - const extDevPaths = args.extensionDevelopmentPath; + async openExtensionDevelopmentHostWindow(windowId: number, args: string[], env: IProcessEnvironment): Promise { + const pargs = parseArgs(args, OPTIONS); + const extDevPaths = pargs.extensionDevelopmentPath; if (extDevPaths) { this.windowsMainService.openExtensionDevelopmentHostWindow(extDevPaths, { context: OpenContext.API, - cli: args, + cli: pargs, userEnv: Object.keys(env).length > 0 ? env : undefined }); } } //#endregion + + private windowById(windowId: number | undefined): ICodeWindow | undefined { + if (typeof windowId !== 'number') { + return undefined; + } + + return this.windowsMainService.getWindowById(windowId); + } } diff --git a/src/vs/platform/electron/node/electron.ts b/src/vs/platform/electron/node/electron.ts index 8a964db380..32f10149ee 100644 --- a/src/vs/platform/electron/node/electron.ts +++ b/src/vs/platform/electron/node/electron.ts @@ -4,12 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { MessageBoxOptions, OpenDevToolsOptions, SaveDialogOptions, OpenDialogOptions, CrashReporterStartOptions } from 'electron'; +import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, SaveDialogOptions, OpenDialogOptions, OpenDialogReturnValue, SaveDialogReturnValue, CrashReporterStartOptions } from 'electron'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWindowOpenable, IOpenEmptyWindowOptions, IOpenedWindow } from 'vs/platform/windows/common/windows'; -import { INativeOpenDialogOptions, MessageBoxReturnValue, SaveDialogReturnValue, OpenDialogReturnValue } from 'vs/platform/dialogs/node/dialogs'; +import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; -import { ParsedArgs } from 'vscode-minimist'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { INativeOpenWindowOptions } from 'vs/platform/windows/node/window'; @@ -75,8 +74,7 @@ export interface IElectronService { // Lifecycle relaunch(options?: { addArgs?: string[], removeArgs?: string[] }): Promise; - reload(): Promise; - closeWorkspace(): Promise; + reload(options?: { disableExtensions?: boolean }): Promise; closeWindow(): Promise; quit(): Promise; @@ -89,5 +87,5 @@ export interface IElectronService { resolveProxy(url: string): Promise; // Debug (TODO@Isidor move into debug IPC channel (https://github.com/microsoft/vscode/issues/81060) - openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise; + openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment): Promise; } diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 6c58087468..4a7dcf5109 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -71,20 +71,23 @@ export interface ParsedArgs { 'driver-verbose'?: boolean; remote?: string; 'disable-user-env-probe'?: boolean; - 'disable-inspect'?: boolean; 'force'?: boolean; 'force-user-env'?: boolean; - - // {{SQL CARBON EDIT}} + // {{SQL CARBON EDIT}} Start aad?: boolean; database?: string; integrated?: boolean; server?: string; user?: string; command?: string; - // {{SQL CARBON EDIT}} - - // node flags + // {{SQL CARBON EDIT}} End + // chromium command line args: https://electronjs.org/docs/all#supported-chrome-command-line-switches + 'no-proxy-server'?: boolean; + 'proxy-server'?: string; + 'proxy-bypass-list'?: string; + 'proxy-pac-url'?: string; + 'inspect'?: string; + 'inspect-brk'?: string; 'js-flags'?: string; 'disable-gpu'?: boolean; 'nolazy'?: boolean; @@ -125,7 +128,7 @@ export interface IEnvironmentService { settingsResource: URI; keybindingsResource: URI; keyboardLayoutResource: URI; - localeResource: URI; + argvResource: URI; // sync resources userDataSyncLogResource: URI; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index d642c873ff..f8c031d794 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -109,10 +109,9 @@ export const OPTIONS: OptionDescriptions> = { 'trace': { type: 'boolean' }, 'trace-category-filter': { type: 'string' }, 'trace-options': { type: 'string' }, - 'disable-inspect': { type: 'boolean' }, 'force-user-env': { type: 'boolean' }, - // {{SQL CARBON EDIT}} + // {{SQL CARBON EDIT}} Start 'command': { type: 'string', alias: 'c', cat: 'o', args: 'command-name', description: localize('commandParameter', 'Name of command to run') }, 'database': { type: 'string', alias: 'D', cat: 'o', args: 'database', description: localize('databaseParameter', 'Database name') }, 'server': { type: 'string', alias: 'S', cat: 'o', args: 'server', description: localize('serverParameter', 'Server name') }, @@ -121,7 +120,14 @@ export const OPTIONS: OptionDescriptions> = { 'integrated': { type: 'boolean', alias: 'E', cat: 'o', description: localize('integratedAuthParameter', 'Use Integrated authentication for server connection') }, // {{SQL CARBON EDIT}} - End + // chromium flags + 'no-proxy-server': { type: 'boolean' }, + 'proxy-server': { type: 'string' }, + 'proxy-bypass-list': { type: 'string' }, + 'proxy-pac-url': { type: 'string' }, 'js-flags': { type: 'string' }, // chrome js flags + 'inspect': { type: 'string' }, + 'inspect-brk': { type: 'string' }, 'nolazy': { type: 'boolean' }, // node inspect '_urls': { type: 'string[]' }, @@ -313,7 +319,6 @@ export function buildVersionMessage(version: string | undefined, commit: string return `${version || localize('unknownVersion', "Unknown version")}\n${commit || localize('unknownCommit', "Unknown commit")}\n${process.arch}`; } - export function addArg(argv: string[], ...args: string[]): string[] { const endOfArgsMarkerIndex = argv.indexOf('--'); if (endOfArgsMarkerIndex === -1) { diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index a56faffef0..7c168b9fd4 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -95,7 +95,6 @@ export class EnvironmentService implements IEnvironmentService { @memoize get userDataPath(): string { const vscodePortable = process.env['VSCODE_PORTABLE']; - if (vscodePortable) { return path.join(vscodePortable, 'user-data'); } @@ -141,7 +140,14 @@ export class EnvironmentService implements IEnvironmentService { get keyboardLayoutResource(): URI { return resources.joinPath(this.userRoamingDataHome, 'keyboardLayout.json'); } @memoize - get localeResource(): URI { return resources.joinPath(this.userRoamingDataHome, 'locale.json'); } + get argvResource(): URI { + const vscodePortable = process.env['VSCODE_PORTABLE']; + if (vscodePortable) { + return URI.file(path.join(vscodePortable, 'argv.json')); + } + + return URI.file(path.join(this.userHome, product.dataFolderName, 'argv.json')); + } @memoize get isExtensionDevelopment(): boolean { return !!this._args.extensionDevelopmentPath; } @@ -182,7 +188,6 @@ export class EnvironmentService implements IEnvironmentService { } const vscodePortable = process.env['VSCODE_PORTABLE']; - if (vscodePortable) { return path.join(vscodePortable, 'extensions'); } diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index 9b18ef6664..cf41128794 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -95,6 +95,14 @@ export interface IColor { defaults: { light: string, dark: string, highContrast: string }; } +export interface IWebviewEditor { + readonly viewType: string; + readonly priority: string; + readonly selector: readonly { + readonly filenamePattern?: string; + }[]; +} + export interface IExtensionContributions { commands?: ICommand[]; configuration?: IConfiguration | IConfiguration[]; @@ -111,6 +119,7 @@ export interface IExtensionContributions { views?: { [location: string]: IView[] }; colors?: IColor[]; localizations?: ILocalization[]; + readonly webviewEditors?: readonly IWebviewEditor[]; } export type ExtensionKind = 'ui' | 'workspace' | 'web'; diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index 035f45207a..25f0036e31 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -163,15 +163,15 @@ export class FileService extends Disposable implements IFileService { private async doResolveFile(resource: URI, options?: IResolveFileOptions): Promise { const provider = await this.withProvider(resource); - const resolveTo = options && options.resolveTo; - const resolveSingleChildDescendants = !!(options && options.resolveSingleChildDescendants); - const resolveMetadata = !!(options && options.resolveMetadata); + const resolveTo = options?.resolveTo; + const resolveSingleChildDescendants = options?.resolveSingleChildDescendants; + const resolveMetadata = options?.resolveMetadata; const stat = await provider.stat(resource); let trie: TernarySearchTree | undefined; - return this.toFileStat(provider, resource, stat, undefined, resolveMetadata, (stat, siblings) => { + return this.toFileStat(provider, resource, stat, undefined, !!resolveMetadata, (stat, siblings) => { // lazy trie to check for recursive resolving if (!trie) { @@ -274,8 +274,7 @@ export class FileService extends Disposable implements IFileService { async createFile(resource: URI, bufferOrReadableOrStream: VSBuffer | VSBufferReadable | VSBufferReadableStream = VSBuffer.fromString(''), options?: ICreateFileOptions): Promise { // validate overwrite - const overwrite = !!(options && options.overwrite); - if (!overwrite && await this.exists(resource)) { + if (!options?.overwrite && await this.exists(resource)) { throw new FileOperationError(localize('fileExists', "File to create already exists ({0})", this.resourceForError(resource)), FileOperationResult.FILE_MODIFIED_SINCE, options); } @@ -507,7 +506,7 @@ export class FileService extends Disposable implements IFileService { } // Return early if file is too large to load - if (options && options.limits) { + if (options?.limits) { if (typeof options.limits.memory === 'number' && stat.size > options.limits.memory) { throw new FileOperationError(localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart and allow it to use more memory"), FileOperationResult.FILE_EXCEED_MEMORY_LIMIT); } @@ -529,7 +528,7 @@ export class FileService extends Disposable implements IFileService { const targetProvider = this.throwIfFileSystemIsReadonly(await this.withReadWriteProvider(target)); // move - const mode = await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'move', overwrite); + const mode = await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'move', !!overwrite); // resolve and send events const fileStat = await this.resolve(target, { resolveMetadata: true }); @@ -543,7 +542,7 @@ export class FileService extends Disposable implements IFileService { const targetProvider = this.throwIfFileSystemIsReadonly(await this.withReadWriteProvider(target)); // copy - const mode = await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'copy', overwrite); + const mode = await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'copy', !!overwrite); // resolve and send events const fileStat = await this.resolve(target, { resolveMetadata: true }); @@ -552,7 +551,7 @@ export class FileService extends Disposable implements IFileService { return fileStat; } - private async doMoveCopy(sourceProvider: IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability, source: URI, targetProvider: IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability, target: URI, mode: 'move' | 'copy', overwrite?: boolean): Promise<'move' | 'copy'> { + private async doMoveCopy(sourceProvider: IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability, source: URI, targetProvider: IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability, target: URI, mode: 'move' | 'copy', overwrite: boolean): Promise<'move' | 'copy'> { if (source.toString() === target.toString()) { return mode; // simulate node.js behaviour here and do a no-op if paths match } @@ -573,7 +572,7 @@ export class FileService extends Disposable implements IFileService { // same provider with fast copy: leverage copy() functionality if (sourceProvider === targetProvider && hasFileFolderCopyCapability(sourceProvider)) { - await sourceProvider.copy(source, target, { overwrite: !!overwrite }); + await sourceProvider.copy(source, target, { overwrite }); } // when copying via buffer/unbuffered, we have to manually @@ -595,7 +594,7 @@ export class FileService extends Disposable implements IFileService { // same provider: leverage rename() functionality if (sourceProvider === targetProvider) { - await sourceProvider.rename(source, target, { overwrite: !!overwrite }); + await sourceProvider.rename(source, target, { overwrite }); return mode; } @@ -744,13 +743,13 @@ export class FileService extends Disposable implements IFileService { const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(resource)); // Validate trash support - const useTrash = !!(options && options.useTrash); + const useTrash = !!options?.useTrash; if (useTrash && !(provider.capabilities & FileSystemProviderCapabilities.Trash)) { throw new Error(localize('err.trash', "Provider does not support trash.")); } // Validate recursive - const recursive = !!(options && options.recursive); + const recursive = !!options?.recursive; if (!recursive && await this.exists(resource)) { const stat = await this.resolve(resource); if (stat.isDirectory && Array.isArray(stat.children) && stat.children.length > 0) { @@ -1062,7 +1061,7 @@ export class FileService extends Disposable implements IFileService { private throwIfTooLarge(totalBytesRead: number, options?: IReadFileOptions): boolean { // Return early if file is too large to load - if (options && options.limits) { + if (options?.limits) { if (typeof options.limits.memory === 'number' && totalBytesRead > options.limits.memory) { throw new FileOperationError(localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart and allow it to use more memory"), FileOperationResult.FILE_EXCEED_MEMORY_LIMIT); } diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index 785d7a30ce..a0e8c1059c 100644 --- a/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -3,14 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { mkdir, open, close, read, write, fdatasync } from 'fs'; +import { mkdir, open, close, read, write, fdatasync, Dirent, Stats } from 'fs'; import { promisify } from 'util'; import { IDisposable, Disposable, toDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; import { IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, FileSystemProviderErrorCode, createFileSystemProviderError, FileSystemProviderError } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { isLinux, isWindows } from 'vs/base/common/platform'; -import { statLink, readdir, unlink, move, copy, readFile, truncate, rimraf, RimRafMode, exists } from 'vs/base/node/pfs'; +import { statLink, unlink, move, copy, readFile, truncate, rimraf, RimRafMode, exists, readdirWithFileTypes } from 'vs/base/node/pfs'; import { normalize, basename, dirname } from 'vs/base/common/path'; import { joinPath } from 'vs/base/common/resources'; import { isEqual } from 'vs/base/common/extpath'; @@ -62,15 +62,8 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro try { const { stat, isSymbolicLink } = await statLink(this.toFilePath(resource)); // cannot use fs.stat() here to support links properly - let type: number; - if (isSymbolicLink) { - type = FileType.SymbolicLink | (stat.isDirectory() ? FileType.Directory : FileType.File); - } else { - type = stat.isFile() ? FileType.File : stat.isDirectory() ? FileType.Directory : FileType.Unknown; - } - return { - type, + type: this.toType(stat, isSymbolicLink), ctime: stat.ctime.getTime(), mtime: stat.mtime.getTime(), size: stat.size @@ -82,13 +75,19 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro async readdir(resource: URI): Promise<[string, FileType][]> { try { - const children = await readdir(this.toFilePath(resource)); + const children = await readdirWithFileTypes(this.toFilePath(resource)); const result: [string, FileType][] = []; await Promise.all(children.map(async child => { try { - const stat = await this.stat(joinPath(resource, child)); - result.push([child, stat.type]); + let type: FileType; + if (child.isSymbolicLink()) { + type = (await this.stat(joinPath(resource, child.name))).type; // always resolve target the link points to if any + } else { + type = this.toType(child); + } + + result.push([child.name, type]); } catch (error) { this.logService.trace(error); // ignore errors for individual entries that can arise from permission denied } @@ -100,6 +99,14 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro } } + private toType(entry: Stats | Dirent, isSymbolicLink = entry.isSymbolicLink()): FileType { + if (isSymbolicLink) { + return FileType.SymbolicLink | (entry.isDirectory() ? FileType.Directory : FileType.File); + } + + return entry.isFile() ? FileType.File : entry.isDirectory() ? FileType.Directory : FileType.Unknown; + } + //#endregion //#region File Reading/Writing @@ -373,7 +380,7 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro try { // Ensure target does not exist - await this.validateTargetDeleted(from, to, 'move', opts && opts.overwrite); + await this.validateTargetDeleted(from, to, 'move', opts.overwrite); // Move await move(fromFilePath, toFilePath); @@ -400,7 +407,7 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro try { // Ensure target does not exist - await this.validateTargetDeleted(from, to, 'copy', opts && opts.overwrite); + await this.validateTargetDeleted(from, to, 'copy', opts.overwrite); // Copy await copy(fromFilePath, toFilePath); diff --git a/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts b/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts index 5cc4cca7e9..fb122db062 100644 --- a/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts +++ b/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts @@ -17,6 +17,9 @@ import { isMacintosh, isLinux } from 'vs/base/common/platform'; import { IDiskFileChange, normalizeFileChanges, ILogMessage } from 'vs/platform/files/node/watcher/watcher'; import { IWatcherRequest, IWatcherService, IWatcherOptions } from 'vs/platform/files/node/watcher/unix/watcher'; import { Emitter, Event } from 'vs/base/common/event'; +import { equals } from 'vs/base/common/arrays'; + +process.noAsar = true; // disable ASAR support in watcher process interface IWatcher { requests: ExtendedWatcherRequest[]; @@ -32,8 +35,8 @@ export class ChokidarWatcherService implements IWatcherService { private static readonly FS_EVENT_DELAY = 50; // aggregate and only emit events when changes have stopped for this duration (in ms) private static readonly EVENT_SPAM_WARNING_THRESHOLD = 60 * 1000; // warn after certain time span of event spam - private _watchers: { [watchPath: string]: IWatcher }; - private _watcherCount: number; + private _watchers: { [watchPath: string]: IWatcher } = Object.create(null); + private _watcherCount = 0; private _pollingInterval?: number; private _usePolling?: boolean; @@ -49,29 +52,30 @@ export class ChokidarWatcherService implements IWatcherService { private readonly _onLogMessage = new Emitter(); readonly onLogMessage: Event = this._onLogMessage.event; - public watch(options: IWatcherOptions): Event { + watch(options: IWatcherOptions): Event { this._pollingInterval = options.pollingInterval; this._usePolling = options.usePolling; this._watchers = Object.create(null); this._watcherCount = 0; + return this.onWatchEvent; } - public setVerboseLogging(enabled: boolean): Promise { + setVerboseLogging(enabled: boolean): Promise { this._verboseLogging = enabled; return Promise.resolve(); } - public setRoots(requests: IWatcherRequest[]): Promise { + setRoots(requests: IWatcherRequest[]): Promise { const watchers = Object.create(null); const newRequests: string[] = []; const requestsByBasePath = normalizeRoots(requests); // evaluate new & remaining watchers - for (let basePath in requestsByBasePath) { - let watcher = this._watchers[basePath]; + for (const basePath in requestsByBasePath) { + const watcher = this._watchers[basePath]; if (watcher && isEqualRequests(watcher.requests, requestsByBasePath[basePath])) { watchers[basePath] = watcher; delete this._watchers[basePath]; @@ -79,13 +83,15 @@ export class ChokidarWatcherService implements IWatcherService { newRequests.push(basePath); } } + // stop all old watchers - for (let path in this._watchers) { + for (const path in this._watchers) { this._watchers[path].stop(); } + // start all new watchers - for (let basePath of newRequests) { - let requests = requestsByBasePath[basePath]; + for (const basePath of newRequests) { + const requests = requestsByBasePath[basePath]; watchers[basePath] = this._watch(basePath, requests); } @@ -94,7 +100,7 @@ export class ChokidarWatcherService implements IWatcherService { } // for test purposes - public get wacherCount() { + get wacherCount() { return this._watcherCount; } @@ -120,9 +126,10 @@ export class ChokidarWatcherService implements IWatcherService { }; const excludes: string[] = []; - // if there's only one request, use the built-in ignore-filterering + const isSingleFolder = requests.length === 1; if (isSingleFolder) { + // if there's only one request, use the built-in ignore-filterering excludes.push(...requests[0].excludes); } @@ -132,6 +139,9 @@ export class ChokidarWatcherService implements IWatcherService { excludes.push('/proc/**', '/sys/**'); } } + + excludes.push('**/*.asar'); // Ensure we never recurse into ASAR archives + watcherOpts.ignored = excludes; // Chokidar fails when the basePath does not match case-identical to the path on disk @@ -219,7 +229,7 @@ export class ChokidarWatcherService implements IWatcherService { } } - let event = { type: eventType, path }; + const event = { type: eventType, path }; // Logging if (this._verboseLogging) { @@ -283,12 +293,14 @@ export class ChokidarWatcherService implements IWatcherService { return watcher; } - public stop(): Promise { - for (let path in this._watchers) { - let watcher = this._watchers[path]; + stop(): Promise { + for (const path in this._watchers) { + const watcher = this._watchers[path]; watcher.stop(); } + this._watchers = Object.create(null); + return Promise.resolve(); } @@ -306,25 +318,28 @@ export class ChokidarWatcherService implements IWatcherService { } function isIgnored(path: string, requests: ExtendedWatcherRequest[]): boolean { - for (let request of requests) { + for (const request of requests) { if (request.path === path) { return false; } + if (extpath.isEqualOrParent(path, request.path)) { if (!request.parsedPattern) { if (request.excludes && request.excludes.length > 0) { - let pattern = `{${request.excludes.join(',')}}`; + const pattern = `{${request.excludes.join(',')}}`; request.parsedPattern = glob.parse(pattern); } else { request.parsedPattern = () => false; } } + const relPath = path.substr(request.path.length + 1); if (!request.parsedPattern(relPath)) { return false; } } } + return true; } @@ -334,11 +349,12 @@ function isIgnored(path: string, requests: ExtendedWatcherRequest[]): boolean { */ export function normalizeRoots(requests: IWatcherRequest[]): { [basePath: string]: IWatcherRequest[] } { requests = requests.sort((r1, r2) => r1.path.localeCompare(r2.path)); + let prevRequest: IWatcherRequest | null = null; - let result: { [basePath: string]: IWatcherRequest[] } = Object.create(null); - for (let request of requests) { - let basePath = request.path; - let ignored = (request.excludes || []).sort(); + const result: { [basePath: string]: IWatcherRequest[] } = Object.create(null); + for (const request of requests) { + const basePath = request.path; + const ignored = (request.excludes || []).sort(); if (prevRequest && (extpath.isEqualOrParent(basePath, prevRequest.path))) { if (!isEqualIgnore(ignored, prevRequest.excludes)) { result[prevRequest.path].push({ path: basePath, excludes: ignored }); @@ -348,29 +364,14 @@ export function normalizeRoots(requests: IWatcherRequest[]): { [basePath: string result[basePath] = [prevRequest]; } } + return result; } -function isEqualRequests(r1: IWatcherRequest[], r2: IWatcherRequest[]) { - if (r1.length !== r2.length) { - return false; - } - for (let k = 0; k < r1.length; k++) { - if (r1[k].path !== r2[k].path || !isEqualIgnore(r1[k].excludes, r2[k].excludes)) { - return false; - } - } - return true; +function isEqualRequests(r1: readonly IWatcherRequest[], r2: readonly IWatcherRequest[]) { + return equals(r1, r2, (a, b) => a.path === b.path && isEqualIgnore(a.excludes, b.excludes)); } -function isEqualIgnore(i1: string[], i2: string[]) { - if (i1.length !== i2.length) { - return false; - } - for (let k = 0; k < i1.length; k++) { - if (i1[k] !== i2[k]) { - return false; - } - } - return true; +function isEqualIgnore(i1: readonly string[], i2: readonly string[]) { + return equals(i1, i2); } diff --git a/src/vs/platform/files/test/node/diskFileService.test.ts b/src/vs/platform/files/test/node/diskFileService.test.ts index d178757f8c..4ef1b3d20b 100644 --- a/src/vs/platform/files/test/node/diskFileService.test.ts +++ b/src/vs/platform/files/test/node/diskFileService.test.ts @@ -21,19 +21,13 @@ import { isLinux, isWindows } from 'vs/base/common/platform'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; import { VSBuffer, VSBufferReadable, toVSBufferReadableStream, VSBufferReadableStream, bufferToReadable, bufferToStream } from 'vs/base/common/buffer'; +import { find } from 'vs/base/common/arrays'; -function getByName(root: IFileStat, name: string): IFileStat | null { +function getByName(root: IFileStat, name: string): IFileStat | undefined { if (root.children === undefined) { - return null; + return undefined; } - - for (const child of root.children) { - if (child.name === name) { - return child; - } - } - - return null; + return find(root.children, child => child.name === name); } function toLineByLineReadable(content: string): VSBufferReadable { diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index 687f3fcb31..1b259194d8 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -219,13 +219,23 @@ export class InstantiationService implements IInstantiationService { // Return a proxy object that's backed by an idle value. That // strategy is to instantiate services in our idle time or when actually // needed but not when injected into a consumer - const idle = new IdleValue(() => this._createInstance(ctor, args, _trace)); + const idle = new IdleValue(() => this._createInstance(ctor, args, _trace)); return new Proxy(Object.create(null), { - get(_target: T, prop: PropertyKey): any { - return (idle.getValue() as any)[prop]; + get(target: any, key: PropertyKey): any { + if (key in target) { + return target[key]; + } + let obj = idle.getValue(); + let prop = obj[key]; + if (typeof prop !== 'function') { + return prop; + } + prop = prop.bind(obj); + target[key] = prop; + return prop; }, set(_target: T, p: PropertyKey, value: any): boolean { - (idle.getValue() as any)[p] = value; + idle.getValue()[p] = value; return true; } }); @@ -242,7 +252,7 @@ const enum TraceType { // {{SQL CARBON EDIT}} - Export trace export class Trace { - private static _None = new class extends Trace { + private static readonly _None = new class extends Trace { constructor() { super(-1, null); } stop() { } branch() { return this; } diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index 1dcb6e92d0..0fe9fbb372 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import * as objects from 'vs/base/common/objects'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { IIssueService, IssueReporterData, IssueReporterFeatures, ProcessExplorerData } from 'vs/platform/issue/node/issue'; -import { BrowserWindow, ipcMain, screen, Event as IpcMainEvent, Display, shell } from 'electron'; +import { BrowserWindow, ipcMain, screen, IpcMainEvent, Display, shell } from 'electron'; import { ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService'; import { PerformanceInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; @@ -81,10 +81,10 @@ export class IssueMainService implements IIssueService { ipcMain.on('vscode:issueReporterClipboard', (event: IpcMainEvent) => { const messageOptions = { - message: localize('issueReporterWriteToClipboard', "There is too much data to send to GitHub. Would you like to write the information to the clipboard so that it can be pasted?"), + message: localize('issueReporterWriteToClipboard', "There is too much data to send to GitHub directly. The data will be copied to the clipboard, please paste it into the GitHub issue page that is opened."), type: 'warning', buttons: [ - localize('yes', "Yes"), + localize('ok', "OK"), localize('cancel', "Cancel") ] }; @@ -156,6 +156,12 @@ export class IssueMainService implements IIssueService { } }); + ipcMain.on('vscode:closeProcessExplorer', (event: IpcMainEvent) => { + if (this._processExplorerWindow) { + this._processExplorerWindow.close(); + } + }); + ipcMain.on('windowsInfoRequest', (event: IpcMainEvent) => { this.launchMainService.getMainProcessInfo().then(info => { event.sender.send('vscode:windowsInfoResponse', info.windows); diff --git a/src/vs/platform/keybinding/common/keybindingsRegistry.ts b/src/vs/platform/keybinding/common/keybindingsRegistry.ts index 8d32769f8b..bdb9741d39 100644 --- a/src/vs/platform/keybinding/common/keybindingsRegistry.ts +++ b/src/vs/platform/keybinding/common/keybindingsRegistry.ts @@ -38,6 +38,7 @@ export interface IKeybindings { export interface IKeybindingRule extends IKeybindings { id: string; weight: number; + args?: any; when: ContextKeyExpr | null | undefined; } @@ -132,7 +133,7 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry { if (actualKb && actualKb.primary) { const kk = createKeybinding(actualKb.primary, OS); if (kk) { - this._registerDefaultKeybinding(kk, rule.id, undefined, rule.weight, 0, rule.when); + this._registerDefaultKeybinding(kk, rule.id, rule.args, rule.weight, 0, rule.when); } } @@ -141,7 +142,7 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry { const k = actualKb.secondary[i]; const kk = createKeybinding(k, OS); if (kk) { - this._registerDefaultKeybinding(kk, rule.id, undefined, rule.weight, -i - 1, rule.when); + this._registerDefaultKeybinding(kk, rule.id, rule.args, rule.weight, -i - 1, rule.when); } } } diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index 0b5fab8552..13f87aefa3 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -159,7 +159,8 @@ suite('AbstractKeybindingService', () => { statusMessageCallsDisposed!.push(message); } }; - } + }, + setFilter() { } }; let resolver = new KeybindingResolver(items, []); diff --git a/src/vs/platform/launch/electron-main/launchMainService.ts b/src/vs/platform/launch/electron-main/launchMainService.ts index 740dafdeff..1cf3e7f43a 100644 --- a/src/vs/platform/launch/electron-main/launchMainService.ts +++ b/src/vs/platform/launch/electron-main/launchMainService.ts @@ -128,7 +128,7 @@ export class LaunchMainService implements ILaunchMainService { // Otherwise check for settings else { const windowConfig = this.configurationService.getValue('window'); - const openWithoutArgumentsInNewWindowConfig = (windowConfig && windowConfig.openWithoutArgumentsInNewWindow) || 'default' /* default */; + const openWithoutArgumentsInNewWindowConfig = windowConfig?.openWithoutArgumentsInNewWindow || 'default' /* default */; switch (openWithoutArgumentsInNewWindowConfig) { case 'on': openNewWindow = true; @@ -196,7 +196,7 @@ export class LaunchMainService implements ILaunchMainService { // In addition, we poll for the wait marker file to be deleted to return. if (waitMarkerFileURI && usedWindows.length === 1 && usedWindows[0]) { return Promise.race([ - this.windowsMainService.waitForWindowCloseOrLoad(usedWindows[0].id), + usedWindows[0].whenClosedOrLoaded, whenDeleted(waitMarkerFileURI.fsPath) ]).then(() => undefined, () => undefined); } @@ -227,7 +227,7 @@ export class LaunchMainService implements ILaunchMainService { mainPID: process.pid, mainArguments: process.argv.slice(1), windows, - screenReader: !!app.isAccessibilitySupportEnabled(), + screenReader: !!app.accessibilitySupportEnabled, gpuFeatureStatus: app.getGPUFeatureStatus() }); } @@ -242,7 +242,8 @@ export class LaunchMainService implements ILaunchMainService { const windows = this.windowsMainService.getWindows(); const promises: Promise[] = windows.map(window => { return new Promise((resolve, reject) => { - if (window.remoteAuthority) { + const remoteAuthority = window.remoteAuthority; + if (remoteAuthority) { const replyChannel = `vscode:getDiagnosticInfoResponse${window.id}`; const args: IDiagnosticInfoOptions = { includeProcesses: options.includeProcesses, @@ -254,14 +255,14 @@ export class LaunchMainService implements ILaunchMainService { ipcMain.once(replyChannel, (_: IpcEvent, data: IRemoteDiagnosticInfo) => { // No data is returned if getting the connection fails. if (!data) { - resolve({ hostName: window.remoteAuthority!, errorMessage: `Unable to resolve connection to '${window.remoteAuthority}'.` }); + resolve({ hostName: remoteAuthority, errorMessage: `Unable to resolve connection to '${remoteAuthority}'.` }); } resolve(data); }); setTimeout(() => { - resolve({ hostName: window.remoteAuthority!, errorMessage: `Fetching remote diagnostics for '${window.remoteAuthority}' timed out.` }); + resolve({ hostName: remoteAuthority, errorMessage: `Fetching remote diagnostics for '${remoteAuthority}' timed out.` }); }, 5000); } else { resolve(); diff --git a/src/vs/platform/lifecycle/common/lifecycleService.ts b/src/vs/platform/lifecycle/common/lifecycleService.ts index b64a7acfb3..4ad35a49d8 100644 --- a/src/vs/platform/lifecycle/common/lifecycleService.ts +++ b/src/vs/platform/lifecycle/common/lifecycleService.ts @@ -23,7 +23,7 @@ export abstract class AbstractLifecycleService extends Disposable implements ILi protected readonly _onShutdown = this._register(new Emitter()); readonly onShutdown: Event = this._onShutdown.event; - protected _startupKind: StartupKind; + protected _startupKind: StartupKind = StartupKind.NewWindow; get startupKind(): StartupKind { return this._startupKind; } private _phase: LifecyclePhase = LifecyclePhase.Starting; diff --git a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts index f3ac9cc67b..327cb41aaf 100644 --- a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts +++ b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts @@ -13,6 +13,7 @@ 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'; +import { ParsedArgs } from 'vs/platform/environment/common/environment'; export const ILifecycleMainService = createDecorator('lifecycleMainService'); @@ -82,6 +83,11 @@ export interface ILifecycleMainService { */ readonly onBeforeWindowUnload: Event; + /** + * Reload a window. All lifecycle event handlers are triggered. + */ + reload(window: ICodeWindow, cli?: ParsedArgs): Promise; + /** * Unload a window for the provided reason. All lifecycle event handlers are triggered. */ @@ -360,6 +366,15 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe }); } + async reload(window: ICodeWindow, cli?: ParsedArgs): Promise { + + // Only reload when the window has not vetoed this + const veto = await this.unload(window, UnloadReason.RELOAD); + if (!veto) { + window.reload(undefined, cli); + } + } + async unload(window: ICodeWindow, reason: UnloadReason): Promise { // Always allow to unload a window that is not yet ready @@ -489,11 +504,11 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe this.logService.trace('Lifecycle#relaunch()'); const args = process.argv.slice(1); - if (options && options.addArgs) { + if (options?.addArgs) { args.push(...options.addArgs); } - if (options && options.removeArgs) { + if (options?.removeArgs) { for (const a of options.removeArgs) { const idx = args.indexOf(a); if (idx >= 0) { diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 06863060f5..3ab9ba36f8 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -430,7 +430,7 @@ export class WorkbenchTree extends Tree { constructor( container: HTMLElement, configuration: ITreeConfiguration, - options: ITreeOptions, + options: ITreeOptions | undefined, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, @@ -798,9 +798,9 @@ export class WorkbenchObjectTree, TFilterData = void> ) { const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, themeService, configurationService, keybindingService, accessibilityService); super(user, container, delegate, renderers, treeOptions); - this.disposables.push(disposable); + this.disposables.add(disposable); this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, contextKeyService, listService, themeService, configurationService, accessibilityService); - this.disposables.push(this.internals); + this.disposables.add(this.internals); } } @@ -825,9 +825,9 @@ export class WorkbenchCompressibleObjectTree, TFilter ) { const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, themeService, configurationService, keybindingService, accessibilityService); super(user, container, delegate, renderers, treeOptions); - this.disposables.push(disposable); + this.disposables.add(disposable); this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, contextKeyService, listService, themeService, configurationService, accessibilityService); - this.disposables.push(this.internals); + this.disposables.add(this.internals); } } @@ -853,9 +853,9 @@ export class WorkbenchDataTree extends DataTree extends Async ) { const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, themeService, configurationService, keybindingService, accessibilityService); super(user, container, delegate, renderers, dataSource, treeOptions); - this.disposables.push(disposable); + this.disposables.add(disposable); this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, contextKeyService, listService, themeService, configurationService, accessibilityService); - this.disposables.push(this.internals); + this.disposables.add(this.internals); } } @@ -910,9 +910,9 @@ export class WorkbenchCompressibleAsyncDataTree e ) { const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, themeService, configurationService, keybindingService, accessibilityService); super(user, container, virtualDelegate, compressionDelegate, renderers, dataSource, treeOptions); - this.disposables.push(disposable); + this.disposables.add(disposable); this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, contextKeyService, listService, themeService, configurationService, accessibilityService); - this.disposables.push(this.internals); + this.disposables.add(this.internals); } } diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 17c24f7be1..25c0c9cdf0 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { isMacintosh, language } from 'vs/base/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { app, shell, Menu, MenuItem, BrowserWindow, MenuItemConstructorOptions, WebContents, Event, Event as KeyboardEvent } from 'electron'; +import { app, shell, Menu, MenuItem, BrowserWindow, MenuItemConstructorOptions, WebContents, Event, KeyboardEvent } from 'electron'; import { OpenContext, IRunActionInWindowRequest, getTitleBarStyle, IRunKeybindingInWindowRequest, IWindowOpenable } from 'vs/platform/windows/common/windows'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -22,6 +22,7 @@ import { URI } from 'vs/base/common/uri'; import { IStateService } from 'vs/platform/state/node/state'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; +import { IElectronMainService } from 'vs/platform/electron/electron-main/electronMainService'; const telemetryFrom = 'menu'; @@ -43,8 +44,8 @@ export class Menubar { private static readonly lastKnownMenubarStorageKey = 'lastKnownMenubarData'; - private willShutdown: boolean; - private appMenuInstalled: boolean; + private willShutdown: boolean | undefined; + private appMenuInstalled: boolean | undefined; private closedLastWindow: boolean; private menuUpdater: RunOnceScheduler; @@ -69,7 +70,8 @@ export class Menubar { @IWorkspacesHistoryMainService private readonly workspacesHistoryMainService: IWorkspacesHistoryMainService, @IStateService private readonly stateService: IStateService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @IElectronMainService private readonly electronMainService: any // {{SQL CARBON EDIT}} remove interface, naster work around ) { this.menuUpdater = new RunOnceScheduler(() => this.doUpdateMenu(), 0); @@ -111,8 +113,8 @@ export class Menubar { // File Menu Items this.fallbackMenuHandlers['workbench.action.files.newUntitledFile'] = () => this.windowsMainService.openEmptyWindow(OpenContext.MENU); this.fallbackMenuHandlers['workbench.action.newWindow'] = () => this.windowsMainService.openEmptyWindow(OpenContext.MENU); - this.fallbackMenuHandlers['workbench.action.files.openFileFolder'] = (menuItem, win, event) => this.windowsMainService.pickFileFolderAndOpen({ forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }); - this.fallbackMenuHandlers['workbench.action.openWorkspace'] = (menuItem, win, event) => this.windowsMainService.pickWorkspaceAndOpen({ forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }); + this.fallbackMenuHandlers['workbench.action.files.openFileFolder'] = (menuItem, win, event) => this.electronMainService.pickFileFolderAndOpen(undefined, { forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }); + this.fallbackMenuHandlers['workbench.action.openWorkspace'] = (menuItem, win, event) => this.electronMainService.pickWorkspaceAndOpen(undefined, { forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }); // Recent Menu Items this.fallbackMenuHandlers['workbench.action.clearRecentFiles'] = () => this.workspacesHistoryMainService.clearRecentlyOpened(); @@ -368,16 +370,17 @@ export class Menubar { const servicesMenu = new Menu(); const services = new MenuItem({ label: nls.localize('mServices', "Services"), role: 'services', submenu: servicesMenu }); const hide = new MenuItem({ label: nls.localize('mHide', "Hide {0}", product.nameLong), role: 'hide', accelerator: 'Command+H' }); - const hideOthers = new MenuItem({ label: nls.localize('mHideOthers', "Hide Others"), role: 'hideothers', accelerator: 'Command+Alt+H' }); + const hideOthers = new MenuItem({ label: nls.localize('mHideOthers', "Hide Others"), role: 'hideOthers', accelerator: 'Command+Alt+H' }); const showAll = new MenuItem({ label: nls.localize('mShowAll', "Show All"), role: 'unhide' }); const quit = new MenuItem(this.likeAction('workbench.action.quit', { label: nls.localize('miQuit', "Quit {0}", product.nameLong), click: () => { + const lastActiveWindow = this.windowsMainService.getLastActiveWindow(); if ( - this.windowsMainService.getWindowCount() === 0 || // allow to quit when no more windows are open - !!BrowserWindow.getFocusedWindow() || // allow to quit when window has focus (fix for https://github.com/Microsoft/vscode/issues/39191) - this.windowsMainService.getLastActiveWindow()!.isMinimized() // allow to quit when window has no focus but is minimized (https://github.com/Microsoft/vscode/issues/63000) + this.windowsMainService.getWindowCount() === 0 || // allow to quit when no more windows are open + !!BrowserWindow.getFocusedWindow() || // allow to quit when window has focus (fix for https://github.com/Microsoft/vscode/issues/39191) + lastActiveWindow?.isMinimized() // allow to quit when window has no focus but is minimized (https://github.com/Microsoft/vscode/issues/63000) ) { - this.windowsMainService.quit(); + this.electronMainService.quit(undefined); } } })); @@ -723,7 +726,7 @@ export class Menubar { let activeBrowserWindow = BrowserWindow.getFocusedWindow(); if (!activeBrowserWindow) { const lastActiveWindow = this.windowsMainService.getLastActiveWindow(); - if (lastActiveWindow && lastActiveWindow.isMinimized()) { + if (lastActiveWindow?.isMinimized()) { activeBrowserWindow = lastActiveWindow.win; } } @@ -755,7 +758,7 @@ export class Menubar { const binding = typeof commandId === 'string' ? this.keybindings[commandId] : undefined; // Apply binding if there is one - if (binding && binding.label) { + if (binding?.label) { // if the binding is native, we can just apply it if (binding.isNative !== false) { diff --git a/src/vs/platform/notification/common/notification.ts b/src/vs/platform/notification/common/notification.ts index 7de3357ae9..26fb37d8a3 100644 --- a/src/vs/platform/notification/common/notification.ts +++ b/src/vs/platform/notification/common/notification.ts @@ -227,6 +227,25 @@ export interface IStatusMessageOptions { hideAfter?: number; } +export enum NotificationsFilter { + + /** + * No filter is enabled. + */ + OFF, + + /** + * All notifications are configured as silent. See + * `INotificationProperties.silent` for more info. + */ + SILENT, + + /** + * All notifications are silent except error notifications. + */ + ERROR +} + /** * A service to bring up notifications and non-modal prompts. * @@ -286,6 +305,13 @@ export interface INotificationService { * @returns a disposable to hide the status message */ status(message: NotificationMessage, options?: IStatusMessageOptions): IDisposable; + + /** + * Allows to configure a filter for notifications. + * + * @param filter the filter to use + */ + setFilter(filter: NotificationsFilter): void; } export class NoOpNotification implements INotificationHandle { diff --git a/src/vs/platform/notification/test/common/testNotificationService.ts b/src/vs/platform/notification/test/common/testNotificationService.ts index 7c38c1de76..cc3a5bb626 100644 --- a/src/vs/platform/notification/test/common/testNotificationService.ts +++ b/src/vs/platform/notification/test/common/testNotificationService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { INotificationService, INotificationHandle, NoOpNotification, Severity, INotification, IPromptChoice, IPromptOptions, IStatusMessageOptions } from 'vs/platform/notification/common/notification'; +import { INotificationService, INotificationHandle, NoOpNotification, Severity, INotification, IPromptChoice, IPromptOptions, IStatusMessageOptions, NotificationsFilter } from 'vs/platform/notification/common/notification'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; export class TestNotificationService implements INotificationService { @@ -35,4 +35,6 @@ export class TestNotificationService implements INotificationService { status(message: string | Error, options?: IStatusMessageOptions): IDisposable { return Disposable.None; } -} \ No newline at end of file + + setFilter(filter: NotificationsFilter): void { } +} diff --git a/src/vs/platform/opener/common/opener.ts b/src/vs/platform/opener/common/opener.ts index db56979b44..7e8abbc118 100644 --- a/src/vs/platform/opener/common/opener.ts +++ b/src/vs/platform/opener/common/opener.ts @@ -9,13 +9,27 @@ import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; export const IOpenerService = createDecorator('openerService'); -type OpenToSideOptions = { readonly openToSide?: boolean }; +type OpenInternalOptions = { + + /** + * Signals that the intent is to open an editor to the side + * of the currently active editor. + */ + readonly openToSide?: boolean; + + /** + * Signals that the editor to open was triggered through a user + * action, such as keyboard or mouse usage. + */ + readonly fromUserGesture?: boolean; +}; + type OpenExternalOptions = { readonly openExternal?: boolean; readonly allowTunneling?: boolean }; -export type OpenOptions = OpenToSideOptions & OpenExternalOptions; +export type OpenOptions = OpenInternalOptions & OpenExternalOptions; export interface IOpener { - open(resource: URI, options?: OpenToSideOptions): Promise; + open(resource: URI, options?: OpenInternalOptions): Promise; open(resource: URI, options?: OpenExternalOptions): Promise; } @@ -53,7 +67,7 @@ export interface IOpenerService { * @param resource A resource * @return A promise that resolves when the opening is done. */ - open(resource: URI, options?: OpenToSideOptions): Promise; + open(resource: URI, options?: OpenInternalOptions): Promise; open(resource: URI, options?: OpenExternalOptions): Promise; resolveExternalUri(resource: URI, options?: { readonly allowTunneling?: boolean }): Promise<{ resolved: URI, dispose(): void }>; diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts index 33652def04..812a687ebc 100644 --- a/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts @@ -61,8 +61,6 @@ export interface IProductConfiguration { readonly productName: string; }; - readonly welcomePage?: string; - readonly enableTelemetry?: boolean; readonly aiConfig?: { readonly asimovKey: string; diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index 026ea96409..0aa48cbee8 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -17,7 +17,7 @@ export interface IProgressService { _serviceBrand: undefined; - withProgress(options: IProgressOptions | IProgressNotificationOptions | IProgressCompositeOptions, task: (progress: IProgress) => Promise, onDidCancel?: () => void): Promise; + withProgress(options: IProgressOptions | IProgressNotificationOptions | IProgressWindowOptions | IProgressCompositeOptions, task: (progress: IProgress) => Promise, onDidCancel?: () => void): Promise; } export interface IProgressIndicator { @@ -59,6 +59,11 @@ export interface IProgressNotificationOptions extends IProgressOptions { readonly secondaryActions?: ReadonlyArray; } +export interface IProgressWindowOptions extends IProgressOptions { + readonly location: ProgressLocation.Window; + readonly command?: string; +} + export interface IProgressCompositeOptions extends IProgressOptions { location: ProgressLocation.Explorer | ProgressLocation.Extensions | ProgressLocation.Scm | string; delay?: number; diff --git a/src/vs/platform/severityIcon/common/severityIcon.ts b/src/vs/platform/severityIcon/common/severityIcon.ts index 82f70c61e9..7cc3a81a96 100644 --- a/src/vs/platform/severityIcon/common/severityIcon.ts +++ b/src/vs/platform/severityIcon/common/severityIcon.ts @@ -4,70 +4,57 @@ *--------------------------------------------------------------------------------------------*/ 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(``); +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { problemsErrorIconForeground, problemsInfoIconForeground, problemsWarningIconForeground } from 'vs/platform/theme/common/colorRegistry'; export namespace SeverityIcon { - export function getSVGData(severity: Severity, theme: ITheme): string { - switch (severity) { - case Severity.Ignore: - const ignoreColor = theme.type === LIGHT ? Color.fromHex('#75BEFF') : Color.fromHex('#007ACC'); - return theme.type === LIGHT ? infoStart + encodeURIComponent(ignoreColor.toString()) + infoEnd - : infoDarkStart + encodeURIComponent(ignoreColor.toString()) + infoDarkEnd; - case Severity.Info: - const infoColor = theme.type === LIGHT ? Color.fromHex('#007ACC') : Color.fromHex('#75BEFF'); - return theme.type === LIGHT ? infoStart + encodeURIComponent(infoColor.toString()) + infoEnd - : infoDarkStart + encodeURIComponent(infoColor.toString()) + infoDarkEnd; - case Severity.Warning: - const warningColor = theme.type === LIGHT ? Color.fromHex('#DDB100') : 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('#A1260D') : 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'; + return 'severity-ignore codicon-info'; case Severity.Info: - return 'severity-icon severity-info'; + return 'codicon-info'; case Severity.Warning: - return 'severity-icon severity-warning'; + return 'codicon-warning'; case Severity.Error: - return 'severity-icon severity-error'; + return 'codicon-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 + + const errorIconForeground = theme.getColor(problemsErrorIconForeground); + if (errorIconForeground) { + collector.addRule(` + .monaco-workbench .zone-widget .codicon-error, + .monaco-workbench .markers-panel .marker-icon.codicon-error, + .monaco-workbench .extensions-viewlet > .extensions .codicon-error { + color: ${errorIconForeground}; + } + `); + } + + const warningIconForeground = theme.getColor(problemsWarningIconForeground); + if (errorIconForeground) { + collector.addRule(` + .monaco-workbench .zone-widget .codicon-warning, + .monaco-workbench .markers-panel .marker-icon.codicon-warning, + .monaco-workbench .extensions-viewlet > .extensions .codicon-warning { + color: ${warningIconForeground}; + } + `); + } + + const infoIconForeground = theme.getColor(problemsInfoIconForeground); + if (errorIconForeground) { + collector.addRule(` + .monaco-workbench .zone-widget .codicon-info, + .monaco-workbench .markers-panel .marker-icon.codicon-info { + color: ${infoIconForeground}; + } + `); + } +}); diff --git a/src/vs/platform/state/node/stateService.ts b/src/vs/platform/state/node/stateService.ts index 3f9094e0b9..3adffe6173 100644 --- a/src/vs/platform/state/node/stateService.ts +++ b/src/vs/platform/state/node/stateService.ts @@ -127,7 +127,7 @@ export class StateService implements IStateService { _serviceBrand: undefined; - private static STATE_FILE = 'storage.json'; + private static readonly STATE_FILE = 'storage.json'; private fileStorage: FileStorage; diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts index ddebe7d2cf..8c1ef09778 100644 --- a/src/vs/platform/storage/browser/storageService.ts +++ b/src/vs/platform/storage/browser/storageService.ts @@ -15,6 +15,7 @@ import { joinPath } from 'vs/base/common/resources'; import { runWhenIdle, RunOnceScheduler } from 'vs/base/common/async'; import { serializableToMap, mapToSerializable } from 'vs/base/common/map'; import { VSBuffer } from 'vs/base/common/buffer'; +import { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; export class BrowserStorageService extends Disposable implements IStorageService { @@ -26,20 +27,20 @@ export class BrowserStorageService extends Disposable implements IStorageService private readonly _onWillSaveState: Emitter = this._register(new Emitter()); readonly onWillSaveState: Event = this._onWillSaveState.event; - private globalStorage: IStorage; - private workspaceStorage: IStorage; + private globalStorage: IStorage | undefined; + private workspaceStorage: IStorage | undefined; - private globalStorageDatabase: FileStorageDatabase; - private workspaceStorageDatabase: FileStorageDatabase; + private globalStorageDatabase: FileStorageDatabase | undefined; + private workspaceStorageDatabase: FileStorageDatabase | undefined; - private globalStorageFile: URI; - private workspaceStorageFile: URI; + private globalStorageFile: URI | undefined; + private workspaceStorageFile: URI | undefined; - private initializePromise: Promise; + private initializePromise: Promise | undefined; private periodicSaveScheduler = this._register(new RunOnceScheduler(() => this.collectState(), 5000)); get hasPendingUpdate(): boolean { - return this.globalStorageDatabase.hasPendingUpdate || this.workspaceStorageDatabase.hasPendingUpdate; + return (!!this.globalStorageDatabase && this.globalStorageDatabase.hasPendingUpdate) || (!!this.workspaceStorageDatabase && this.workspaceStorageDatabase.hasPendingUpdate); } constructor( @@ -137,16 +138,18 @@ export class BrowserStorageService extends Disposable implements IStorageService } private getStorage(scope: StorageScope): IStorage { - return scope === StorageScope.GLOBAL ? this.globalStorage : this.workspaceStorage; + return assertIsDefined(scope === StorageScope.GLOBAL ? this.globalStorage : this.workspaceStorage); } async logStorage(): Promise { + const [globalStorage, workspaceStorage, globalStorageFile, workspaceStorageFile] = assertAllDefined(this.globalStorage, this.workspaceStorage, this.globalStorageFile, this.workspaceStorageFile); + const result = await Promise.all([ - this.globalStorage.items, - this.workspaceStorage.items + globalStorage.items, + workspaceStorage.items ]); - return logStorage(result[0], result[1], this.globalStorageFile.toString(), this.workspaceStorageFile.toString()); + return logStorage(result[0], result[1], globalStorageFile.toString(), workspaceStorageFile.toString()); } async migrate(toWorkspace: IWorkspaceInitializationPayload): Promise { diff --git a/src/vs/platform/storage/node/storageIpc.ts b/src/vs/platform/storage/node/storageIpc.ts index 064f5a5db8..5569dcbd49 100644 --- a/src/vs/platform/storage/node/storageIpc.ts +++ b/src/vs/platform/storage/node/storageIpc.ts @@ -29,7 +29,7 @@ interface ISerializableItemsChangeEvent { export class GlobalStorageDatabaseChannel extends Disposable implements IServerChannel { - private static STORAGE_CHANGE_DEBOUNCE_TIME = 100; + private static readonly STORAGE_CHANGE_DEBOUNCE_TIME = 100; private readonly _onDidChangeItems: Emitter = this._register(new Emitter()); readonly onDidChangeItems: Event = this._onDidChangeItems.event; diff --git a/src/vs/platform/storage/node/storageMainService.ts b/src/vs/platform/storage/node/storageMainService.ts index 475408aaba..04525f9f94 100644 --- a/src/vs/platform/storage/node/storageMainService.ts +++ b/src/vs/platform/storage/node/storageMainService.ts @@ -87,7 +87,7 @@ export class StorageMainService extends Disposable implements IStorageMainServic _serviceBrand: undefined; - private static STORAGE_NAME = 'state.vscdb'; + private static readonly STORAGE_NAME = 'state.vscdb'; private readonly _onDidChangeStorage: Emitter = this._register(new Emitter()); readonly onDidChangeStorage: Event = this._onDidChangeStorage.event; diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts index 589ba36397..04b5f17921 100644 --- a/src/vs/platform/storage/node/storageService.ts +++ b/src/vs/platform/storage/node/storageService.ts @@ -15,13 +15,14 @@ import { copy, exists, mkdirp, writeFile } from 'vs/base/node/pfs'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceInitializationPayload, isWorkspaceIdentifier, isSingleFolderWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; export class NativeStorageService extends Disposable implements IStorageService { _serviceBrand: undefined; - private static WORKSPACE_STORAGE_NAME = 'state.vscdb'; - private static WORKSPACE_META_NAME = 'workspace.json'; + private static readonly WORKSPACE_STORAGE_NAME = 'state.vscdb'; + private static readonly WORKSPACE_META_NAME = 'workspace.json'; private readonly _onDidChangeStorage: Emitter = this._register(new Emitter()); readonly onDidChangeStorage: Event = this._onDidChangeStorage.event; @@ -31,11 +32,11 @@ export class NativeStorageService extends Disposable implements IStorageService private globalStorage: IStorage; - private workspaceStoragePath: string; - private workspaceStorage: IStorage; - private workspaceStorageListener: IDisposable; + private workspaceStoragePath: string | undefined; + private workspaceStorage: IStorage | undefined; + private workspaceStorageListener: IDisposable | undefined; - private initializePromise: Promise; + private initializePromise: Promise | undefined; constructor( globalStorageDatabase: IStorageDatabase, @@ -191,22 +192,24 @@ export class NativeStorageService extends Disposable implements IStorageService // Do it await Promise.all([ - this.globalStorage.close(), - this.workspaceStorage.close() + this.getStorage(StorageScope.GLOBAL).close(), + this.getStorage(StorageScope.WORKSPACE).close() ]); } private getStorage(scope: StorageScope): IStorage { - return scope === StorageScope.GLOBAL ? this.globalStorage : this.workspaceStorage; + return assertIsDefined(scope === StorageScope.GLOBAL ? this.globalStorage : this.workspaceStorage); } async logStorage(): Promise { + const [workspaceStorage, workspaceStoragePath] = assertAllDefined(this.workspaceStorage, this.workspaceStoragePath); + const result = await Promise.all([ this.globalStorage.items, - this.workspaceStorage.items + workspaceStorage.items ]); - logStorage(result[0], result[1], this.environmentService.globalStorageHome, this.workspaceStoragePath); + logStorage(result[0], result[1], this.environmentService.globalStorageHome, workspaceStoragePath); } async migrate(toWorkspace: IWorkspaceInitializationPayload): Promise { @@ -215,7 +218,7 @@ export class NativeStorageService extends Disposable implements IStorageService } // Close workspace DB to be able to copy - await this.workspaceStorage.close(); + await this.getStorage(StorageScope.WORKSPACE).close(); // Prepare new workspace storage folder const result = await this.prepareWorkspaceStorageFolder(toWorkspace); @@ -223,7 +226,7 @@ export class NativeStorageService extends Disposable implements IStorageService const newWorkspaceStoragePath = join(result.path, NativeStorageService.WORKSPACE_STORAGE_NAME); // Copy current storage over to new workspace storage - await copy(this.workspaceStoragePath, newWorkspaceStoragePath); + await copy(assertIsDefined(this.workspaceStoragePath), newWorkspaceStoragePath); // Recreate and init workspace storage return this.createWorkspaceStorage(newWorkspaceStoragePath).init(); diff --git a/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts b/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts index 72b734b71b..08af83cba1 100644 --- a/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts +++ b/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts @@ -13,8 +13,16 @@ export const lastSessionDateStorageKey = 'telemetry.lastSessionDate'; import * as Platform from 'vs/base/common/platform'; import * as uuid from 'vs/base/common/uuid'; import { cleanRemoteAuthority } from 'vs/platform/telemetry/common/telemetryUtils'; +import { mixin } from 'vs/base/common/objects'; -export async function resolveWorkbenchCommonProperties(storageService: IStorageService, commit: string | undefined, version: string | undefined, machineId: string, remoteAuthority?: string): Promise<{ [name: string]: string | undefined }> { +export async function resolveWorkbenchCommonProperties( + storageService: IStorageService, + commit: string | undefined, + version: string | undefined, + machineId: string, + remoteAuthority?: string, + resolveAdditionalProperties?: () => { [key: string]: any } +): Promise<{ [name: string]: string | undefined }> { const result: { [name: string]: string | undefined; } = Object.create(null); const firstSessionDate = storageService.get(firstSessionDateStorageKey, StorageScope.GLOBAL)!; const lastSessionDate = storageService.get(lastSessionDateStorageKey, StorageScope.GLOBAL)!; @@ -68,6 +76,10 @@ export async function resolveWorkbenchCommonProperties(storageService: IStorageS } }); + if (resolveAdditionalProperties) { + mixin(result, resolveAdditionalProperties()); + } + return result; } diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index 8a2a9ee44c..9a459bfbcf 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -24,8 +24,8 @@ export interface ITelemetryServiceConfig { export class TelemetryService implements ITelemetryService { - static IDLE_START_EVENT_NAME = 'UserIdleStart'; - static IDLE_STOP_EVENT_NAME = 'UserIdleStop'; + static readonly IDLE_START_EVENT_NAME = 'UserIdleStart'; + static readonly IDLE_STOP_EVENT_NAME = 'UserIdleStop'; _serviceBrand: undefined; diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index ecf75b5803..227a44f7bf 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -20,7 +20,7 @@ export const NullTelemetryService = new class implements ITelemetryService { return this.publicLog(eventName, data as ITelemetryData); } setEnabled() { } - isOptedIn: true; + isOptedIn = true; getTelemetryInfo(): Promise { return Promise.resolve({ instanceId: 'someValue.instanceId', diff --git a/src/vs/platform/telemetry/node/commonProperties.ts b/src/vs/platform/telemetry/node/commonProperties.ts index ded3703356..858a132001 100644 --- a/src/vs/platform/telemetry/node/commonProperties.ts +++ b/src/vs/platform/telemetry/node/commonProperties.ts @@ -7,7 +7,6 @@ import * as Platform from 'vs/base/common/platform'; import * as os from 'os'; import * as uuid from 'vs/base/common/uuid'; import { readFile } from 'vs/base/node/pfs'; -import { mixin } from 'vs/base/common/objects'; import product from 'vs/platform/product/common/product'; // {{SQL CARBON EDIT}} const productObject = product; // {{SQL CARBON EDIT}} @@ -18,8 +17,7 @@ export async function resolveCommonProperties( machineId: string | undefined, msftInternalDomains: string[] | undefined, installSourcePath: string, - product?: string, - resolveAdditionalProperties?: () => { [key: string]: any } + product?: string ): Promise<{ [name: string]: string | boolean | undefined; }> { const result: { [name: string]: string | boolean | undefined; } = Object.create(null); // {{SQL CARBON EDIT}} start @@ -54,9 +52,9 @@ export async function resolveCommonProperties( result['common.product'] = productObject.nameShort || 'desktop'; // {{SQL CARBON EDIT}} result['common.application.name'] = productObject.nameLong; // {{SQL CARBON EDIT}} - // const msftInternal = verifyMicrosoftInternalDomain(msftInternalDomains || []); {{SQL CARBON EDIT}} remove msft internal + // const msftInternal = verifyMicrosoftInternalDomain(msftInternalDomains || []); {{SQL CARBON EDIT}} remove msftinternal // if (msftInternal) { - // // __GDPR__COMMON__ "common.msftInternal" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + // // __GDPR__COMMON__ "common.msftInternal" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } // result['common.msftInternal'] = msftInternal; // } @@ -95,24 +93,14 @@ export async function resolveCommonProperties( // ignore error } - if (resolveAdditionalProperties) { - mixin(result, resolveAdditionalProperties()); - } - return result; } -function verifyMicrosoftInternalDomain(domainList: string[]): boolean { +function verifyMicrosoftInternalDomain(domainList: readonly string[]): boolean { if (!process || !process.env || !process.env['USERDNSDOMAIN']) { return false; } const domain = process.env['USERDNSDOMAIN']!.toLowerCase(); - for (let msftDomain of domainList) { - if (domain === msftDomain) { - return true; - } - } - - return false; + return domainList.some(msftDomain => domain === msftDomain); } diff --git a/src/vs/platform/telemetry/node/workbenchCommonProperties.ts b/src/vs/platform/telemetry/node/workbenchCommonProperties.ts index 12b090e76a..b60e49cee6 100644 --- a/src/vs/platform/telemetry/node/workbenchCommonProperties.ts +++ b/src/vs/platform/telemetry/node/workbenchCommonProperties.ts @@ -17,10 +17,9 @@ export async function resolveWorkbenchCommonProperties( machineId: string, msftInternalDomains: string[] | undefined, installSourcePath: string, - remoteAuthority?: string, - resolveAdditionalProperties?: () => { [key: string]: any } + remoteAuthority?: string ): Promise<{ [name: string]: string | boolean | undefined }> { - const result = await resolveCommonProperties(commit, version, machineId, msftInternalDomains, installSourcePath, undefined, resolveAdditionalProperties); + const result = await resolveCommonProperties(commit, version, machineId, msftInternalDomains, installSourcePath, undefined); const instanceId = storageService.get(instanceStorageKey, StorageScope.GLOBAL)!; const firstSessionDate = storageService.get(firstSessionDateStorageKey, StorageScope.GLOBAL)!; const lastSessionDate = storageService.get(lastSessionDateStorageKey, StorageScope.GLOBAL)!; diff --git a/src/vs/platform/telemetry/test/browser/commonProperties.test.ts b/src/vs/platform/telemetry/test/browser/commonProperties.test.ts new file mode 100644 index 0000000000..cd571e4f15 --- /dev/null +++ b/src/vs/platform/telemetry/test/browser/commonProperties.test.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/browser/workbenchCommonProperties'; +import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage'; + +suite('Browser Telemetry - common properties', function () { + + const commit: string = (undefined)!; + const version: string = (undefined)!; + let testStorageService: IStorageService; + + setup(() => { + testStorageService = new InMemoryStorageService(); + }); + + test('mixes in additional properties', async function () { + const resolveCommonTelemetryProperties = () => { + return { + 'userId': '1' + }; + }; + + const props = await resolveWorkbenchCommonProperties(testStorageService, commit, version, 'someMachineId', undefined, resolveCommonTelemetryProperties); + + assert.ok('commitHash' in props); + assert.ok('sessionID' in props); + assert.ok('timestamp' in props); + assert.ok('common.platform' in props); + assert.ok('common.timesincesessionstart' in props); + assert.ok('common.sequence' in props); + assert.ok('version' in props); + assert.ok('common.firstSessionDate' in props, 'firstSessionDate'); + assert.ok('common.lastSessionDate' in props, 'lastSessionDate'); + assert.ok('common.isNewSession' in props, 'isNewSession'); + assert.ok('common.machineId' in props, 'machineId'); + + assert.equal(props['userId'], '1'); + }); + + test('mixes in additional dyanmic properties', async function () { + let i = 1; + const resolveCommonTelemetryProperties = () => { + return Object.defineProperties({}, { + 'userId': { + get: () => { + return i++; + }, + enumerable: true + } + }); + }; + + const props = await resolveWorkbenchCommonProperties(testStorageService, commit, version, 'someMachineId', undefined, resolveCommonTelemetryProperties); + assert.equal(props['userId'], '1'); + + const props2 = await resolveWorkbenchCommonProperties(testStorageService, commit, version, 'someMachineId', undefined, resolveCommonTelemetryProperties); + assert.equal(props2['userId'], '2'); + }); +}); diff --git a/src/vs/platform/telemetry/test/electron-browser/commonProperties.test.ts b/src/vs/platform/telemetry/test/electron-browser/commonProperties.test.ts index 2a1e2fe22f..8a564e9e08 100644 --- a/src/vs/platform/telemetry/test/electron-browser/commonProperties.test.ts +++ b/src/vs/platform/telemetry/test/electron-browser/commonProperties.test.ts @@ -81,52 +81,4 @@ suite('Telemetry - common properties', function () { value2 = props['common.timesincesessionstart']; assert.ok(value1 !== value2, 'timesincesessionstart'); }); - - test.skip('mixes in additional properties', async function () { // {{SQL CARBON EDIT}} skip test - const resolveCommonTelemetryProperties = () => { - return { - 'userId': '1' - }; - }; - - const props = await resolveWorkbenchCommonProperties(testStorageService, commit, version, 'someMachineId', undefined, installSource, undefined, resolveCommonTelemetryProperties); - - assert.ok('commitHash' in props); - assert.ok('sessionID' in props); - assert.ok('timestamp' in props); - assert.ok('common.platform' in props); - assert.ok('common.nodePlatform' in props); - assert.ok('common.nodeArch' in props); - assert.ok('common.timesincesessionstart' in props); - assert.ok('common.sequence' in props); - assert.ok('common.platformVersion' in props, 'platformVersion'); - assert.ok('version' in props); - assert.ok('common.firstSessionDate' in props, 'firstSessionDate'); - assert.ok('common.lastSessionDate' in props, 'lastSessionDate'); - assert.ok('common.isNewSession' in props, 'isNewSession'); - assert.ok('common.instanceId' in props, 'instanceId'); - assert.ok('common.machineId' in props, 'machineId'); - - assert.equal(props['userId'], '1'); - }); - - test('mixes in additional dyanmic properties', async function () { - let i = 1; - const resolveCommonTelemetryProperties = () => { - return Object.defineProperties({}, { - 'userId': { - get: () => { - return i++; - }, - enumerable: true - } - }); - }; - - const props = await resolveWorkbenchCommonProperties(testStorageService, commit, version, 'someMachineId', undefined, installSource, undefined, resolveCommonTelemetryProperties); - assert.equal(props['userId'], '1'); - - const props2 = await resolveWorkbenchCommonProperties(testStorageService, commit, version, 'someMachineId', undefined, installSource, undefined, resolveCommonTelemetryProperties); - assert.equal(props2['userId'], '2'); - }); }); diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 515421dba0..a87a5b53b2 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -103,7 +103,7 @@ class ColorRegistry implements IColorRegistry { public registerColor(id: string, defaults: ColorDefaults | null, description: string, needsTransparency = false, deprecationMessage?: string): ColorIdentifier { let colorContribution: ColorContribution = { id, description, defaults, needsTransparency, deprecationMessage }; this.colorsById[id] = colorContribution; - let propertySchema: IJSONSchema = { type: 'string', description, format: 'color-hex', default: '#ff0000' }; + let propertySchema: IJSONSchema = { type: 'string', description, format: 'color-hex', defaultSnippets: [{ body: '#ff0000' }] }; if (deprecationMessage) { propertySchema.deprecationMessage = deprecationMessage; } @@ -229,24 +229,6 @@ export const simpleCheckboxBackground = registerColor('checkbox.background', { d export const simpleCheckboxForeground = registerColor('checkbox.foreground', { dark: selectForeground, light: selectForeground, hc: selectForeground }, nls.localize('checkbox.foreground', "Foreground color of checkbox widget.")); export const simpleCheckboxBorder = registerColor('checkbox.border', { dark: selectBorder, light: selectBorder, hc: selectBorder }, nls.localize('checkbox.border', "Border color of checkbox widget.")); -export const listFocusBackground = registerColor('list.focusBackground', { dark: '#062F4A', light: '#D6EBFF', hc: null }, nls.localize('listFocusBackground', "List/Tree background color for the focused item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); -export const listFocusForeground = registerColor('list.focusForeground', { dark: null, light: null, hc: null }, nls.localize('listFocusForeground', "List/Tree foreground color for the focused item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); -export const listActiveSelectionBackground = registerColor('list.activeSelectionBackground', { dark: '#094771', light: '#0074E8', hc: null }, nls.localize('listActiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); -export const listActiveSelectionForeground = registerColor('list.activeSelectionForeground', { dark: Color.white, light: Color.white, hc: null }, nls.localize('listActiveSelectionForeground', "List/Tree foreground color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); -export const listInactiveSelectionBackground = registerColor('list.inactiveSelectionBackground', { dark: '#37373D', light: '#E4E6F1', hc: null }, nls.localize('listInactiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); -export const listInactiveSelectionForeground = registerColor('list.inactiveSelectionForeground', { dark: null, light: null, hc: null }, nls.localize('listInactiveSelectionForeground', "List/Tree foreground color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); -export const listInactiveFocusBackground = registerColor('list.inactiveFocusBackground', { dark: null, light: null, hc: null }, nls.localize('listInactiveFocusBackground', "List/Tree background color for the focused item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); -export const listHoverBackground = registerColor('list.hoverBackground', { dark: '#2A2D2E', light: '#F0F0F0', hc: null }, nls.localize('listHoverBackground', "List/Tree background when hovering over items using the mouse.")); -export const listHoverForeground = registerColor('list.hoverForeground', { dark: null, light: null, hc: null }, nls.localize('listHoverForeground', "List/Tree foreground when hovering over items using the mouse.")); -export const listDropBackground = registerColor('list.dropBackground', { dark: listFocusBackground, light: listFocusBackground, hc: null }, nls.localize('listDropBackground', "List/Tree drag and drop background when moving items around using the mouse.")); -export const listHighlightForeground = registerColor('list.highlightForeground', { dark: '#0097fb', light: '#0066BF', hc: focusBorder }, nls.localize('highlight', 'List/Tree foreground color of the match highlights when searching inside the list/tree.')); -export const listInvalidItemForeground = registerColor('list.invalidItemForeground', { dark: '#B89500', light: '#B89500', hc: '#B89500' }, nls.localize('invalidItemForeground', 'List/Tree foreground color for invalid items, for example an unresolved root in explorer.')); -export const listErrorForeground = registerColor('list.errorForeground', { dark: '#F88070', light: '#B01011', hc: null }, nls.localize('listErrorForeground', 'Foreground color of list items containing errors.')); -export const listWarningForeground = registerColor('list.warningForeground', { dark: '#CCA700', light: '#855F00', hc: null }, nls.localize('listWarningForeground', 'Foreground color of list items containing warnings.')); -export const listFilterWidgetBackground = registerColor('listFilterWidget.background', { light: '#efc1ad', dark: '#653723', hc: Color.black }, nls.localize('listFilterWidgetBackground', 'Background color of the type filter widget in lists and trees.')); -export const listFilterWidgetOutline = registerColor('listFilterWidget.outline', { dark: Color.transparent, light: Color.transparent, hc: '#f38518' }, nls.localize('listFilterWidgetOutline', 'Outline color of the type filter widget in lists and trees.')); -export const listFilterWidgetNoMatchesOutline = registerColor('listFilterWidget.noMatchesOutline', { dark: '#BE1100', light: '#BE1100', hc: contrastBorder }, nls.localize('listFilterWidgetNoMatchesOutline', 'Outline color of the type filter widget in lists and trees, when there are no matches.')); -export const treeIndentGuidesStroke = registerColor('tree.indentGuidesStroke', { dark: '#585858', light: '#a9a9a9', hc: '#a9a9a9' }, nls.localize('treeIndentGuidesStroke', "Tree stroke color for the indentation guides.")); export const pickerGroupForeground = registerColor('pickerGroup.foreground', { dark: '#3794FF', light: '#0066BF', hc: Color.white }, nls.localize('pickerGroupForeground', "Quick picker color for grouping labels.")); export const pickerGroupBorder = registerColor('pickerGroup.border', { dark: '#3F3F46', light: '#CCCEDB', hc: Color.white }, nls.localize('pickerGroupBorder', "Quick picker color for grouping borders.")); @@ -265,14 +247,6 @@ export const scrollbarSliderActiveBackground = registerColor('scrollbarSlider.ac export const progressBarBackground = registerColor('progressBar.background', { dark: Color.fromHex('#0E70C0'), light: Color.fromHex('#0E70C0'), hc: contrastBorder }, nls.localize('progressBarBackground', "Background color of the progress bar that can show for long running operations.")); -export const menuBorder = registerColor('menu.border', { dark: null, light: null, hc: contrastBorder }, nls.localize('menuBorder', "Border color of menus.")); -export const menuForeground = registerColor('menu.foreground', { dark: selectForeground, light: foreground, hc: selectForeground }, nls.localize('menuForeground', "Foreground color of menu items.")); -export const menuBackground = registerColor('menu.background', { dark: selectBackground, light: selectBackground, hc: selectBackground }, nls.localize('menuBackground', "Background color of menu items.")); -export const menuSelectionForeground = registerColor('menu.selectionForeground', { dark: listActiveSelectionForeground, light: listActiveSelectionForeground, hc: listActiveSelectionForeground }, nls.localize('menuSelectionForeground', "Foreground color of the selected menu item in menus.")); -export const menuSelectionBackground = registerColor('menu.selectionBackground', { dark: listActiveSelectionBackground, light: listActiveSelectionBackground, hc: listActiveSelectionBackground }, nls.localize('menuSelectionBackground', "Background color of the selected menu item in menus.")); -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.')); @@ -360,6 +334,41 @@ export const diffRemovedOutline = registerColor('diffEditor.removedTextBorder', export const diffBorder = registerColor('diffEditor.border', { dark: null, light: null, hc: contrastBorder }, nls.localize('diffEditorBorder', 'Border color between the two text editors.')); +/** + * List and tree colors + */ +export const listFocusBackground = registerColor('list.focusBackground', { dark: '#062F4A', light: '#D6EBFF', hc: null }, nls.localize('listFocusBackground', "List/Tree background color for the focused item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); +export const listFocusForeground = registerColor('list.focusForeground', { dark: null, light: null, hc: null }, nls.localize('listFocusForeground', "List/Tree foreground color for the focused item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); +export const listActiveSelectionBackground = registerColor('list.activeSelectionBackground', { dark: '#094771', light: '#0074E8', hc: null }, nls.localize('listActiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); +export const listActiveSelectionForeground = registerColor('list.activeSelectionForeground', { dark: Color.white, light: Color.white, hc: null }, nls.localize('listActiveSelectionForeground', "List/Tree foreground color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); +export const listInactiveSelectionBackground = registerColor('list.inactiveSelectionBackground', { dark: '#37373D', light: '#E4E6F1', hc: null }, nls.localize('listInactiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); +export const listInactiveSelectionForeground = registerColor('list.inactiveSelectionForeground', { dark: null, light: null, hc: null }, nls.localize('listInactiveSelectionForeground', "List/Tree foreground color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); +export const listInactiveFocusBackground = registerColor('list.inactiveFocusBackground', { dark: null, light: null, hc: null }, nls.localize('listInactiveFocusBackground', "List/Tree background color for the focused item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); +export const listHoverBackground = registerColor('list.hoverBackground', { dark: '#2A2D2E', light: '#F0F0F0', hc: null }, nls.localize('listHoverBackground', "List/Tree background when hovering over items using the mouse.")); +export const listHoverForeground = registerColor('list.hoverForeground', { dark: null, light: null, hc: null }, nls.localize('listHoverForeground', "List/Tree foreground when hovering over items using the mouse.")); +export const listDropBackground = registerColor('list.dropBackground', { dark: listFocusBackground, light: listFocusBackground, hc: null }, nls.localize('listDropBackground', "List/Tree drag and drop background when moving items around using the mouse.")); +export const listHighlightForeground = registerColor('list.highlightForeground', { dark: '#0097fb', light: '#0066BF', hc: focusBorder }, nls.localize('highlight', 'List/Tree foreground color of the match highlights when searching inside the list/tree.')); +export const listInvalidItemForeground = registerColor('list.invalidItemForeground', { dark: '#B89500', light: '#B89500', hc: '#B89500' }, nls.localize('invalidItemForeground', 'List/Tree foreground color for invalid items, for example an unresolved root in explorer.')); +export const listErrorForeground = registerColor('list.errorForeground', { dark: '#F88070', light: '#B01011', hc: null }, nls.localize('listErrorForeground', 'Foreground color of list items containing errors.')); +export const listWarningForeground = registerColor('list.warningForeground', { dark: '#CCA700', light: '#855F00', hc: null }, nls.localize('listWarningForeground', 'Foreground color of list items containing warnings.')); +export const listFilterWidgetBackground = registerColor('listFilterWidget.background', { light: '#efc1ad', dark: '#653723', hc: Color.black }, nls.localize('listFilterWidgetBackground', 'Background color of the type filter widget in lists and trees.')); +export const listFilterWidgetOutline = registerColor('listFilterWidget.outline', { dark: Color.transparent, light: Color.transparent, hc: '#f38518' }, nls.localize('listFilterWidgetOutline', 'Outline color of the type filter widget in lists and trees.')); +export const listFilterWidgetNoMatchesOutline = registerColor('listFilterWidget.noMatchesOutline', { dark: '#BE1100', light: '#BE1100', hc: contrastBorder }, nls.localize('listFilterWidgetNoMatchesOutline', 'Outline color of the type filter widget in lists and trees, when there are no matches.')); +export const listFilterMatchHighlight = registerColor('list.filterMatchBackground', { dark: editorFindMatchHighlight, light: editorFindMatchHighlight, hc: null }, nls.localize('listFilterMatchHighlight', 'Background color of the filtered match.')); +export const listFilterMatchHighlightBorder = registerColor('list.filterMatchBorder', { dark: editorFindMatchHighlightBorder, light: editorFindMatchHighlightBorder, hc: contrastBorder }, nls.localize('listFilterMatchHighlightBorder', 'Border color of the filtered match.')); +export const treeIndentGuidesStroke = registerColor('tree.indentGuidesStroke', { dark: '#585858', light: '#a9a9a9', hc: '#a9a9a9' }, nls.localize('treeIndentGuidesStroke', "Tree stroke color for the indentation guides.")); + +/** + * Menu colors + */ +export const menuBorder = registerColor('menu.border', { dark: null, light: null, hc: contrastBorder }, nls.localize('menuBorder', "Border color of menus.")); +export const menuForeground = registerColor('menu.foreground', { dark: selectForeground, light: foreground, hc: selectForeground }, nls.localize('menuForeground', "Foreground color of menu items.")); +export const menuBackground = registerColor('menu.background', { dark: selectBackground, light: selectBackground, hc: selectBackground }, nls.localize('menuBackground', "Background color of menu items.")); +export const menuSelectionForeground = registerColor('menu.selectionForeground', { dark: listActiveSelectionForeground, light: listActiveSelectionForeground, hc: listActiveSelectionForeground }, nls.localize('menuSelectionForeground', "Foreground color of the selected menu item in menus.")); +export const menuSelectionBackground = registerColor('menu.selectionBackground', { dark: listActiveSelectionBackground, light: listActiveSelectionBackground, hc: listActiveSelectionBackground }, nls.localize('menuSelectionBackground', "Background color of the selected menu item in menus.")); +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.")); + /** * Snippet placeholder colors */ @@ -406,8 +415,11 @@ export const overviewRulerFindMatchForeground = registerColor('editorOverviewRul export const overviewRulerSelectionHighlightForeground = registerColor('editorOverviewRuler.selectionHighlightForeground', { dark: '#A0A0A0CC', light: '#A0A0A0CC', hc: '#A0A0A0CC' }, nls.localize('overviewRulerSelectionHighlightForeground', 'Overview ruler marker color for selection highlights. The color must not be opaque so as not to hide underlying decorations.'), true); export const minimapFindMatch = registerColor('minimap.findMatchHighlight', { light: '#d18616', dark: '#d18616', hc: '#AB5A00' }, nls.localize('minimapFindMatchHighlight', 'Minimap marker color for find matches.'), true); -export const minimapSelection = registerColor('minimap.selectionHighlight', { light: '#ADD6FF', dark: '#264F78', hc: '#f3f518' }, nls.localize('minimapSelectionHighlight', 'Minimap marker color for the current editor selection.'), true); +export const minimapSelection = registerColor('minimap.selectionHighlight', { light: '#ADD6FF', dark: '#264F78', hc: '#ffffff' }, nls.localize('minimapSelectionHighlight', 'Minimap marker color for the editor selection.'), true); +export const problemsErrorIconForeground = registerColor('problemsErrorIcon.foreground', { dark: editorErrorForeground, light: editorErrorForeground, hc: editorErrorForeground }, nls.localize('problemsErrorIconForeground', "The color used for the problems error icon.")); +export const problemsWarningIconForeground = registerColor('problemsWarningIcon.foreground', { dark: editorWarningForeground, light: editorWarningForeground, hc: editorWarningForeground }, nls.localize('problemsWarningIconForeground', "The color used for the problems warning icon.")); +export const problemsInfoIconForeground = registerColor('problemsInfoIcon.foreground', { dark: editorInfoForeground, light: editorInfoForeground, hc: editorInfoForeground }, nls.localize('problemsInfoIconForeground', "The color used for the problems info icon.")); // ----- color functions @@ -475,7 +487,7 @@ function lessProminent(colorValue: ColorValue, backgroundColorValue: ColorValue, /** * @param colorValue Resolve a color value in the context of a theme */ -function resolveColorValue(colorValue: ColorValue | null, theme: ITheme): Color | undefined { +export function resolveColorValue(colorValue: ColorValue | null, theme: ITheme): Color | undefined { if (colorValue === null) { return undefined; } else if (typeof colorValue === 'string') { diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index d76a7d634a..acb757b9a4 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; -import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, badgeBackground, badgeForeground, progressBarBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, editorWidgetBorder, inputValidationInfoForeground, inputValidationWarningForeground, inputValidationErrorForeground, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuBorder, menuSeparatorBackground, darken, listFilterWidgetOutline, listFilterWidgetNoMatchesOutline, listFilterWidgetBackground, editorWidgetBackground, treeIndentGuidesStroke, editorWidgetForeground, simpleCheckboxBackground, simpleCheckboxBorder, simpleCheckboxForeground } from 'vs/platform/theme/common/colorRegistry'; +import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, badgeBackground, badgeForeground, progressBarBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, editorWidgetBorder, inputValidationInfoForeground, inputValidationWarningForeground, inputValidationErrorForeground, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuBorder, menuSeparatorBackground, darken, listFilterWidgetOutline, listFilterWidgetNoMatchesOutline, listFilterWidgetBackground, editorWidgetBackground, treeIndentGuidesStroke, editorWidgetForeground, simpleCheckboxBackground, simpleCheckboxBorder, simpleCheckboxForeground, ColorValue, resolveColorValue } from 'vs/platform/theme/common/colorRegistry'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; @@ -20,7 +20,7 @@ export interface IThemable { } export interface IColorMapping { - [optionsKey: string]: ColorIdentifier | ColorFunction | undefined; + [optionsKey: string]: ColorValue | undefined; } export interface IComputedStyles { @@ -30,11 +30,9 @@ export interface IComputedStyles { export function computeStyles(theme: ITheme, styleMap: IColorMapping): IComputedStyles { const styles = Object.create(null) as IComputedStyles; for (let key in styleMap) { - const value = styleMap[key as string]; - if (typeof value === 'string') { - styles[key] = theme.getColor(value); - } else if (typeof value === 'function') { - styles[key] = value(theme); + const value = styleMap[key]; + if (value) { + styles[key] = resolveColorValue(value, theme); } } diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts index 783d487d5d..c3ee032fef 100644 --- a/src/vs/platform/update/electron-main/updateService.darwin.ts +++ b/src/vs/platform/update/electron-main/updateService.darwin.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as electron from 'electron'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { memoize } from 'vs/base/common/decorators'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -20,7 +20,7 @@ export class DarwinUpdateService extends AbstractUpdateService { _serviceBrand: undefined; - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); @memoize private get onRawError(): Event { return Event.fromNodeEventEmitter(electron.autoUpdater, 'error', (_, message) => message); } @memoize private get onRawUpdateNotAvailable(): Event { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-not-available'); } @@ -104,6 +104,6 @@ export class DarwinUpdateService extends AbstractUpdateService { } dispose(): void { - this.disposables = dispose(this.disposables); + this.disposables.dispose(); } } diff --git a/src/vs/platform/url/common/url.ts b/src/vs/platform/url/common/url.ts index 3fc2a53c13..e759d722ad 100644 --- a/src/vs/platform/url/common/url.ts +++ b/src/vs/platform/url/common/url.ts @@ -9,8 +9,19 @@ import { IDisposable } from 'vs/base/common/lifecycle'; export const IURLService = createDecorator('urlService'); +export interface IOpenURLOptions { + + /** + * If not provided or `false`, signals that the + * URL to open did not originate from the product + * but outside. As such, a confirmation dialog + * might be shown to the user. + */ + trusted?: boolean; +} + export interface IURLHandler { - handleURL(uri: URI): Promise; + handleURL(uri: URI, options?: IOpenURLOptions): Promise; } export interface IURLService { @@ -24,7 +35,7 @@ export interface IURLService { */ create(options?: Partial): URI; - open(url: URI): Promise; + open(url: URI, options?: IOpenURLOptions): Promise; registerHandler(handler: IURLHandler): IDisposable; } diff --git a/src/vs/platform/url/common/urlIpc.ts b/src/vs/platform/url/common/urlIpc.ts index 9c7f569a4b..7c71c0bc8a 100644 --- a/src/vs/platform/url/common/urlIpc.ts +++ b/src/vs/platform/url/common/urlIpc.ts @@ -6,7 +6,7 @@ import { IChannel, IServerChannel, IClientRouter, IConnectionHub, Client } from 'vs/base/parts/ipc/common/ipc'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; -import { IURLHandler } from 'vs/platform/url/common/url'; +import { IURLHandler, IOpenURLOptions } from 'vs/platform/url/common/url'; import { CancellationToken } from 'vs/base/common/cancellation'; import { first } from 'vs/base/common/arrays'; @@ -31,7 +31,7 @@ export class URLHandlerChannelClient implements IURLHandler { constructor(private channel: IChannel) { } - handleURL(uri: URI): Promise { + handleURL(uri: URI, options?: IOpenURLOptions): Promise { return this.channel.call('handleURL', uri.toJSON()); } } @@ -49,11 +49,12 @@ export class URLHandlerRouter implements IClientRouter { const uri = URI.revive(arg); if (uri && uri.query) { - const match = /\bwindowId=([^&]+)/.exec(uri.query); + const match = /\bwindowId=(\d+)/.exec(uri.query); if (match) { const windowId = match[1]; - const connection = first(hub.connections, c => c.ctx === windowId); + const regex = new RegExp(`window:${windowId}`); + const connection = first(hub.connections, c => regex.test(c.ctx)); if (connection) { return connection; diff --git a/src/vs/platform/url/common/urlService.ts b/src/vs/platform/url/common/urlService.ts index bb4ba2113d..a69a37e0d8 100644 --- a/src/vs/platform/url/common/urlService.ts +++ b/src/vs/platform/url/common/urlService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; +import { IURLService, IURLHandler, IOpenURLOptions } from 'vs/platform/url/common/url'; import { URI, UriComponents } from 'vs/base/common/uri'; import { values } from 'vs/base/common/map'; import { first } from 'vs/base/common/async'; @@ -17,9 +17,9 @@ export abstract class AbstractURLService extends Disposable implements IURLServi abstract create(options?: Partial): URI; - open(uri: URI): Promise { + open(uri: URI, options?: IOpenURLOptions): Promise { const handlers = values(this.handlers); - return first(handlers.map(h => () => h.handleURL(uri)), undefined, false).then(val => val || false); + return first(handlers.map(h => () => h.handleURL(uri, options)), undefined, false).then(val => val || false); } registerHandler(handler: IURLHandler): IDisposable { diff --git a/src/vs/platform/url/electron-main/electronUrlListener.ts b/src/vs/platform/url/electron-main/electronUrlListener.ts index 5c68c4dfb0..3f0316c496 100644 --- a/src/vs/platform/url/electron-main/electronUrlListener.ts +++ b/src/vs/platform/url/electron-main/electronUrlListener.ts @@ -8,10 +8,11 @@ import { IURLService } from 'vs/platform/url/common/url'; import product from 'vs/platform/product/common/product'; import { app, Event as ElectronEvent } from 'electron'; import { URI } from 'vs/base/common/uri'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; import { isWindows } from 'vs/base/common/platform'; import { coalesce } from 'vs/base/common/arrays'; +import { disposableTimeout } from 'vs/base/common/async'; function uriFromRawUrl(url: string): URI | null { try { @@ -23,7 +24,10 @@ function uriFromRawUrl(url: string): URI | null { export class ElectronURLListener { - private disposables: IDisposable[] = []; + private uris: URI[] = []; + private retryCount = 0; + private flushDisposable: IDisposable = Disposable.None; + private disposables = new DisposableStore(); constructor( initial: string | string[], @@ -36,12 +40,7 @@ export class ElectronURLListener { ...globalBuffer ]; - const buffer = coalesce(rawBuffer.map(uriFromRawUrl)); - const flush = () => buffer.forEach(uri => { - if (uri) { - urlService.open(uri); - } - }); + this.uris = coalesce(rawBuffer.map(uriFromRawUrl)); if (isWindows) { app.setAsDefaultProtocolClient(product.urlProtocol, process.execPath, ['--open-url', '--']); @@ -63,13 +62,37 @@ export class ElectronURLListener { .length > 0; if (isWindowReady) { - flush(); + this.flush(); } else { - Event.once(windowsMainService.onWindowReady)(flush); + Event.once(windowsMainService.onWindowReady)(this.flush, this, this.disposables); } } + private async flush(): Promise { + if (this.retryCount++ > 10) { + return; + } + + const uris: URI[] = []; + + for (const uri of this.uris) { + const handled = await this.urlService.open(uri); + + if (!handled) { + uris.push(uri); + } + } + + if (uris.length === 0) { + return; + } + + this.uris = uris; + this.flushDisposable = disposableTimeout(() => this.flush(), 500); + } + dispose(): void { - this.disposables = dispose(this.disposables); + this.disposables.dispose(); + this.flushDisposable.dispose(); } } diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 67b3d6673c..7acedf8262 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -70,16 +70,16 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser async sync(): Promise { if (!this.configurationService.getValue('configurationSync.enableExtensions')) { - this.logService.trace('Extensions: Skipping synchronising extensions as it is disabled.'); + this.logService.trace('Extensions: Skipping synchronizing extensions as it is disabled.'); return false; } if (this.status !== SyncStatus.Idle) { - this.logService.trace('Extensions: Skipping synchronising extensions as it is running already.'); + this.logService.trace('Extensions: Skipping synchronizing extensions as it is running already.'); return false; } - this.logService.trace('Extensions: Started synchronising extensions...'); + this.logService.trace('Extensions: Started synchronizing extensions...'); this.setStatus(SyncStatus.Syncing); try { @@ -88,13 +88,13 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser this.setStatus(SyncStatus.Idle); if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { // Rejected as there is a new remote version. Syncing again, - this.logService.info('Extensions: Failed to synchronise extensions as there is a new remote version available. Synchronising again...'); + this.logService.info('Extensions: Failed to synchronise extensions as there is a new remote version available. Synchronizing again...'); return this.sync(); } throw e; } - this.logService.trace('Extensions: Finised synchronising extensions.'); + this.logService.trace('Extensions: Finised synchronizing extensions.'); this.setStatus(SyncStatus.Idle); return true; } @@ -129,7 +129,7 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser const { added, removed, updated, remote } = this.merge(localExtensions, remoteExtensions, lastSyncExtensions); if (!added.length && !removed.length && !updated.length && !remote) { - this.logService.trace('Extensions: No changes found during synchronising extensions.'); + this.logService.trace('Extensions: No changes found during synchronizing extensions.'); } if (added.length || removed.length || updated.length) { @@ -162,7 +162,7 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser const ignoredExtensions = this.configurationService.getValue('configurationSync.extensionsToIgnore') || []; // First time sync if (!remoteExtensions) { - this.logService.info('Extensions: Remote extensions does not exist. Synchronising extensions for the first time.'); + this.logService.info('Extensions: Remote extensions does not exist. Synchronizing extensions for the first time.'); return { added: [], removed: [], updated: [], remote: localExtensions.filter(({ identifier }) => ignoredExtensions.some(id => id.toLowerCase() === identifier.id.toLowerCase())) }; } diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 2543e0e647..caaf88f550 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -81,27 +81,27 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { async sync(_continue?: boolean): Promise { if (!this.configurationService.getValue('configurationSync.enableSettings')) { - this.logService.trace('Settings: Skipping synchronising settings as it is disabled.'); + this.logService.trace('Settings: Skipping synchronizing settings as it is disabled.'); return false; } if (_continue) { - this.logService.info('Settings: Resumed synchronising settings'); + this.logService.info('Settings: Resumed synchronizing settings'); return this.continueSync(); } if (this.status !== SyncStatus.Idle) { - this.logService.trace('Settings: Skipping synchronising settings as it is running already.'); + this.logService.trace('Settings: Skipping synchronizing settings as it is running already.'); return false; } - this.logService.trace('Settings: Started synchronising settings...'); + this.logService.trace('Settings: Started synchronizing settings...'); this.setStatus(SyncStatus.Syncing); try { const result = await this.getPreview(); if (result.hasConflicts) { - this.logService.info('Settings: Detected conflicts while synchronising settings.'); + this.logService.info('Settings: Detected conflicts while synchronizing settings.'); this.setStatus(SyncStatus.HasConflicts); return false; } @@ -112,12 +112,12 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { this.setStatus(SyncStatus.Idle); if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { // Rejected as there is a new remote version. Syncing again, - this.logService.info('Settings: Failed to synchronise settings as there is a new remote version available. Synchronising again...'); + this.logService.info('Settings: Failed to synchronise settings as there is a new remote version available. Synchronizing again...'); return this.sync(); } if (e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) { // Rejected as there is a new local version. Syncing again. - this.logService.info('Settings: Failed to synchronise settings as there is a new local version available. Synchronising again...'); + this.logService.info('Settings: Failed to synchronise settings as there is a new local version available. Synchronizing again...'); return this.sync(); } throw e; @@ -128,7 +128,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { if (this.syncPreviewResultPromise) { this.syncPreviewResultPromise.cancel(); this.syncPreviewResultPromise = null; - this.logService.info('Settings: Stopped synchronising settings.'); + this.logService.info('Settings: Stopped synchronizing settings.'); } this.fileService.del(this.environmentService.settingsSyncPreviewResource); this.setStatus(SyncStatus.Idle); @@ -158,7 +158,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { let { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged } = await this.syncPreviewResultPromise; if (!hasLocalChanged && !hasRemoteChanged) { - this.logService.trace('Settings: No changes found during synchronising settings.'); + this.logService.trace('Settings: No changes found during synchronizing settings.'); } if (hasLocalChanged) { this.logService.info('Settings: Updating local settings'); @@ -178,10 +178,10 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { // Delete the preview await this.fileService.del(this.environmentService.settingsSyncPreviewResource); } else { - this.logService.trace('Settings: No changes found during synchronising settings.'); + this.logService.trace('Settings: No changes found during synchronizing settings.'); } - this.logService.trace('Settings: Finised synchronising settings.'); + this.logService.trace('Settings: Finised synchronizing settings.'); this.syncPreviewResultPromise = null; this.setStatus(SyncStatus.Idle); } @@ -235,7 +235,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { // First time syncing to remote else if (fileContent) { - this.logService.info('Settings: Remote settings does not exist. Synchronising settings for the first time.'); + this.logService.info('Settings: Remote settings does not exist. Synchronizing settings for the first time.'); hasRemoteChanged = true; previewContent = fileContent.value.toString(); } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index cf793d87cd..dc2b337d3a 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -38,13 +38,13 @@ export function registerConfiguration(): IDisposable { }, 'configurationSync.enableSettings': { type: 'boolean', - description: localize('configurationSync.enableSettings', "When enabled settings are synchronised while synchronising configuration."), + description: localize('configurationSync.enableSettings', "When enabled settings are synchronised while synchronizing configuration."), default: true, scope: ConfigurationScope.APPLICATION, }, 'configurationSync.enableExtensions': { type: 'boolean', - description: localize('configurationSync.enableExtensions', "When enabled extensions are synchronised while synchronising configuration."), + description: localize('configurationSync.enableExtensions', "When enabled extensions are synchronised while synchronizing configuration."), default: true, scope: ConfigurationScope.APPLICATION, }, @@ -91,6 +91,7 @@ export interface IUserData { } export enum UserDataSyncStoreErrorCode { + Unauthroized = 'Unauthroized', Rejected = 'Rejected', Unknown = 'Unknown' } diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 561ada7643..eda99c7245 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource, IUserDataSyncLogService, UserDataSyncStoreError, UserDataSyncStoreErrorCode } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; @@ -127,7 +127,7 @@ export class UserDataAutoSync extends Disposable { @IConfigurationService private readonly configurationService: IConfigurationService, @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncLogService private readonly userDataSyncLogService: IUserDataSyncLogService, + @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IAuthTokenService private readonly authTokenService: IAuthTokenService, ) { super(); @@ -147,13 +147,13 @@ export class UserDataAutoSync extends Disposable { this.enabled = enabled; if (this.enabled) { - this.userDataSyncLogService.info('Syncing configuration started'); + this.logService.info('Syncing configuration started'); this.sync(true); return; } else { if (stopIfDisabled) { this.userDataSyncService.stop(); - this.userDataSyncLogService.info('Syncing configuration stopped.'); + this.logService.info('Syncing configuration stopped.'); } } @@ -164,7 +164,14 @@ export class UserDataAutoSync extends Disposable { try { await this.userDataSyncService.sync(); } catch (e) { - this.userDataSyncLogService.error(e); + if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Unauthroized) { + if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Unauthroized && this.authTokenService.status === AuthTokenStatus.Disabled) { + this.logService.error('Sync failed because the server requires authorization. Please enable authorization.'); + } else { + this.logService.error(e); + } + } + this.logService.error(e); } if (loop) { await timeout(1000 * 5); // Loop sync for every 5s. diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index 769bb69835..62121a6acc 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, } from 'vs/base/common/lifecycle'; -import { IUserData, IUserDataSyncStoreService, UserDataSyncStoreErrorCode, UserDataSyncStoreError, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, IUserDataSyncStoreService, UserDataSyncStoreErrorCode, UserDataSyncStoreError } from 'vs/platform/userDataSync/common/userDataSync'; import { IProductService } from 'vs/platform/product/common/productService'; import { IRequestService, asText, isSuccess } from 'vs/platform/request/common/request'; import { URI } from 'vs/base/common/uri'; @@ -22,7 +22,6 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn constructor( @IProductService private readonly productService: IProductService, @IRequestService private readonly requestService: IRequestService, - @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IAuthTokenService private readonly authTokenService: IAuthTokenService, ) { super(); @@ -100,10 +99,11 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn const context = await this.requestService.request(options, token); if (context.res.statusCode === 401) { - // Not Authorized - this.logService.info('Authroization Failed.'); - this.authTokenService.refreshToken(); - Promise.reject('Authroization Failed.'); + if (this.authTokenService.status !== AuthTokenStatus.Disabled) { + this.authTokenService.refreshToken(); + } + // Throw Unauthorized Error + throw new UserDataSyncStoreError('Unauthorized', UserDataSyncStoreErrorCode.Unauthroized); } return context; diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 70f9079c08..2324a228e2 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -65,6 +65,17 @@ export function isFileToOpen(uriToOpen: IWindowOpenable): uriToOpen is IFileToOp export type MenuBarVisibility = 'default' | 'visible' | 'toggle' | 'hidden' | 'compact'; +export function getMenuBarVisibility(configurationService: IConfigurationService, environment: IEnvironmentService, isExtensionDevelopment = environment.isExtensionDevelopment): MenuBarVisibility { + const titleBarStyle = getTitleBarStyle(configurationService, environment, isExtensionDevelopment); + const menuBarVisibility = configurationService.getValue('window.menuBarVisibility'); + + if (titleBarStyle === 'native' && menuBarVisibility === 'compact') { + return 'default'; + } else { + return menuBarVisibility; + } +} + export interface IWindowsConfiguration { window: IWindowSettings; } diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 9583c670c0..027a321db0 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { OpenContext, IWindowConfiguration, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; -import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -13,6 +12,7 @@ import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { URI } from 'vs/base/common/uri'; import { Rectangle, BrowserWindow } from 'electron'; +import { IDisposable } from 'vs/base/common/lifecycle'; export interface IWindowState { width?: number; @@ -30,10 +30,16 @@ export const enum WindowMode { Fullscreen } -export interface ICodeWindow { +export interface ICodeWindow extends IDisposable { + + readonly onClose: Event; + readonly onDestroy: Event; + + readonly whenClosedOrLoaded: Promise; + readonly id: number; readonly win: BrowserWindow; - readonly config: IWindowConfiguration; + readonly config: IWindowConfiguration | undefined; readonly openedFolderUri?: URI; readonly openedWorkspace?: IWorkspaceIdentifier; @@ -48,6 +54,9 @@ export interface ICodeWindow { readonly isReady: boolean; ready(): Promise; + setReady(): void; + + readonly hasHiddenTitleBarStyle: boolean; addTabbedWindow(window: ICodeWindow): void; @@ -62,20 +71,19 @@ export interface ICodeWindow { send(channel: string, ...args: any[]): void; sendWhenReady(channel: string, ...args: any[]): void; + readonly isFullScreen: boolean; toggleFullScreen(): void; - isFullScreen(): boolean; + isMinimized(): boolean; - hasHiddenTitleBarStyle(): boolean; + setRepresentedFilename(name: string): void; - getRepresentedFilename(): string; + getRepresentedFilename(): string | undefined; + handleTitleDoubleClick(): void; updateTouchBar(items: ISerializableCommandAction[][]): void; - setReady(): void; serializeWindowState(): IWindowState; - - dispose(): void; } export const IWindowsMainService = createDecorator('windowsMainService'); @@ -91,17 +99,11 @@ export interface IWindowsMainService { readonly onWindowReady: Event; readonly onWindowsCountChanged: Event; - readonly onWindowClose: Event; open(openConfig: IOpenConfiguration): ICodeWindow[]; openEmptyWindow(context: OpenContext, options?: IOpenEmptyWindowOptions): ICodeWindow[]; openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string[], openConfig: IOpenConfiguration): ICodeWindow[]; - pickFileFolderAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise; - pickFolderAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise; - pickFileAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise; - pickWorkspaceAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise; - sendToFocused(channel: string, ...args: any[]): void; sendToAll(channel: string, payload: any, windowIdsToIgnore?: number[]): void; @@ -110,11 +112,6 @@ export interface IWindowsMainService { getWindowById(windowId: number): ICodeWindow | undefined; getWindows(): ICodeWindow[]; getWindowCount(): number; - - waitForWindowCloseOrLoad(windowId: number): Promise; - reload(win: ICodeWindow, cli?: ParsedArgs): void; - closeWorkspace(win: ICodeWindow): void; - quit(): void; } export interface IOpenConfiguration { diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts similarity index 81% rename from src/vs/code/electron-main/windows.ts rename to src/vs/platform/windows/electron-main/windowsMainService.ts index 14677b3be3..ddd61b0988 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -19,23 +19,19 @@ import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMai import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, IPathsToWaitFor, isFileToOpen, isWorkspaceToOpen, isFolderToOpen, IWindowOpenable, IOpenEmptyWindowOptions, IAddFoldersRequest } from 'vs/platform/windows/common/windows'; -import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri } from 'vs/platform/windows/node/window'; import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; import product from 'vs/platform/product/common/product'; -import { ITelemetryService, ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; import { IProcessEnvironment, isMacintosh, isWindows } from 'vs/base/common/platform'; import { IWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, hasWorkspaceFileExtension, IRecent } from 'vs/platform/workspaces/common/workspaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; -import { dirExists } from 'vs/base/node/pfs'; import { getComparisonKey, isEqual, normalizePath, originalFSPath, hasTrailingPathSeparator, removeTrailingPathSeparator } from 'vs/base/common/resources'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; -import { restoreWindowsState, WindowsStateStorageData, getWindowsStateStoreData } from 'vs/code/electron-main/windowsStateStorage'; +import { restoreWindowsState, WindowsStateStorageData, getWindowsStateStoreData } from 'vs/platform/windows/electron-main/windowsStateStorage'; import { getWorkspaceIdentifier, IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { once } from 'vs/base/common/functional'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -44,11 +40,6 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { isWindowsDriveLetter, toSlashes } from 'vs/base/common/extpath'; import { CharCode } from 'vs/base/common/charCode'; -const enum WindowError { - UNRESPONSIVE = 1, - CRASHED = 2 -} - export interface IWindowState { workspace?: IWorkspaceIdentifier; folderUri?: URI; @@ -158,7 +149,7 @@ interface IWorkspacePathToOpen { label?: string; } -export class WindowsManager extends Disposable implements IWindowsMainService { +export class WindowsMainService extends Disposable implements IWindowsMainService { _serviceBrand: undefined; @@ -175,9 +166,6 @@ export class WindowsManager extends Disposable implements IWindowsMainService { private readonly _onWindowClose = this._register(new Emitter()); readonly onWindowClose: CommonEvent = this._onWindowClose.event; - private readonly _onWindowLoad = this._register(new Emitter()); - readonly onWindowLoad: CommonEvent = this._onWindowLoad.event; - private readonly _onWindowsCountChanged = this._register(new Emitter()); readonly onWindowsCountChanged: CommonEvent = this._onWindowsCountChanged.event; @@ -189,7 +177,6 @@ export class WindowsManager extends Disposable implements IWindowsMainService { @IEnvironmentService private readonly environmentService: IEnvironmentService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IBackupMainService private readonly backupMainService: IBackupMainService, - @ITelemetryService private readonly telemetryService: ITelemetryService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkspacesHistoryMainService private readonly workspacesHistoryMainService: IWorkspacesHistoryMainService, @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, @@ -198,7 +185,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { ) { super(); - this.windowsState = restoreWindowsState(this.stateService.getItem(WindowsManager.windowsStateStorageKey)); + this.windowsState = restoreWindowsState(this.stateService.getItem(WindowsMainService.windowsStateStorageKey)); if (!Array.isArray(this.windowsState.openedWindows)) { this.windowsState.openedWindows = []; } @@ -237,13 +224,16 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // React to HC color scheme changes (Windows) if (isWindows) { - systemPreferences.on('inverted-color-scheme-changed', () => { - if (systemPreferences.isInvertedColorScheme()) { + const onHighContrastChange = () => { + if (systemPreferences.isInvertedColorScheme() || systemPreferences.isHighContrastColorScheme()) { this.sendToAll('vscode:enterHighContrast'); } else { this.sendToAll('vscode:leaveHighContrast'); } - }); + }; + + systemPreferences.on('inverted-color-scheme-changed', () => onHighContrastChange()); + systemPreferences.on('high-contrast-color-scheme-changed', () => onHighContrastChange()); } // Handle various lifecycle events around windows @@ -312,7 +302,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { if (!currentWindowsState.lastActiveWindow) { let activeWindow = this.getLastActiveWindow(); if (!activeWindow || activeWindow.isExtensionDevelopmentHost) { - activeWindow = WindowsManager.WINDOWS.filter(window => !window.isExtensionDevelopmentHost)[0]; + activeWindow = WindowsMainService.WINDOWS.filter(window => !window.isExtensionDevelopmentHost)[0]; } if (activeWindow) { @@ -321,7 +311,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { } // 2.) Find extension host window - const extensionHostWindow = WindowsManager.WINDOWS.filter(window => window.isExtensionDevelopmentHost && !window.isExtensionTestHost)[0]; + const extensionHostWindow = WindowsMainService.WINDOWS.filter(window => window.isExtensionDevelopmentHost && !window.isExtensionTestHost)[0]; if (extensionHostWindow) { currentWindowsState.lastPluginDevelopmentHostWindow = this.toWindowState(extensionHostWindow); } @@ -332,11 +322,11 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // so if we ever want to persist the UI state of the last closed window (window count === 1), it has // to come from the stored lastClosedWindowState on Win/Linux at least if (this.getWindowCount() > 1) { - currentWindowsState.openedWindows = WindowsManager.WINDOWS.filter(window => !window.isExtensionDevelopmentHost).map(window => this.toWindowState(window)); + currentWindowsState.openedWindows = WindowsMainService.WINDOWS.filter(window => !window.isExtensionDevelopmentHost).map(window => this.toWindowState(window)); } // Persist - this.stateService.setItem(WindowsManager.windowsStateStorageKey, getWindowsStateStoreData(currentWindowsState)); + this.stateService.setItem(WindowsMainService.windowsStateStorageKey, getWindowsStateStoreData(currentWindowsState)); } // See note on #onBeforeShutdown() for details how these events are flowing @@ -382,6 +372,19 @@ export class WindowsManager extends Disposable implements IWindowsMainService { }; } + openEmptyWindow(context: OpenContext, options?: IOpenEmptyWindowOptions): ICodeWindow[] { + let cli = this.environmentService.args; + const remote = options?.remoteAuthority; + if (cli && (cli.remote !== remote)) { + cli = { ...cli, remote }; + } + + const forceReuseWindow = options?.forceReuseWindow; + const forceNewWindow = !forceReuseWindow; + + return this.open({ context, cli, forceEmpty: true, forceNewWindow, forceReuseWindow }); + } + open(openConfig: IOpenConfiguration): ICodeWindow[] { this.logService.trace('windowsManager#open'); openConfig = this.validateOpenConfig(openConfig); @@ -459,7 +462,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // 1.) focus last active window if we are not instructed to open any paths if (focusLastActive) { - const lastActiveWindow = usedWindows.filter(window => window.backupPath === this.windowsState.lastActiveWindow!.backupPath); + const lastActiveWindow = usedWindows.filter(window => this.windowsState.lastActiveWindow && window.backupPath === this.windowsState.lastActiveWindow.backupPath); if (lastActiveWindow.length) { lastActiveWindow[0].focus(); focusLastOpened = false; @@ -472,9 +475,9 @@ export class WindowsManager extends Disposable implements IWindowsMainService { for (let i = usedWindows.length - 1; i >= 0; i--) { const usedWindow = usedWindows[i]; if ( - (usedWindow.openedWorkspace && workspacesToRestore.some(workspace => workspace.workspace.id === usedWindow.openedWorkspace!.id)) || // skip over restored workspace - (usedWindow.openedFolderUri && foldersToRestore.some(uri => isEqual(uri, usedWindow.openedFolderUri))) || // skip over restored folder - (usedWindow.backupPath && emptyToRestore.some(empty => empty.backupFolder === basename(usedWindow.backupPath!))) // skip over restored empty window + (usedWindow.openedWorkspace && workspacesToRestore.some(workspace => usedWindow.openedWorkspace && workspace.workspace.id === usedWindow.openedWorkspace.id)) || // skip over restored workspace + (usedWindow.openedFolderUri && foldersToRestore.some(uri => isEqual(uri, usedWindow.openedFolderUri))) || // skip over restored folder + (usedWindow.backupPath && emptyToRestore.some(empty => usedWindow.backupPath && empty.backupFolder === basename(usedWindow.backupPath))) // skip over restored empty window ) { continue; } @@ -512,7 +515,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // process can continue. We do this by deleting the waitMarkerFilePath. const waitMarkerFileURI = openConfig.waitMarkerFileURI; if (openConfig.context === OpenContext.CLI && waitMarkerFileURI && usedWindows.length === 1 && usedWindows[0]) { - this.waitForWindowCloseOrLoad(usedWindows[0].id).then(() => fs.unlink(waitMarkerFileURI.fsPath, _error => undefined)); + usedWindows[0].whenClosedOrLoaded.then(() => fs.unlink(waitMarkerFileURI.fsPath, _error => undefined)); } return usedWindows; @@ -559,13 +562,13 @@ export class WindowsManager extends Disposable implements IWindowsMainService { const fileToCheck = fileInputs.filesToOpenOrCreate[0] || fileInputs.filesToDiff[0]; // only look at the windows with correct authority - const windows = WindowsManager.WINDOWS.filter(window => window.remoteAuthority === fileInputs!.remoteAuthority); + const windows = WindowsMainService.WINDOWS.filter(window => fileInputs && window.remoteAuthority === fileInputs.remoteAuthority); const bestWindowOrFolder = findBestWindowOrFolderForFile({ windows, newWindow: openFilesInNewWindow, context: openConfig.context, - fileUri: fileToCheck && fileToCheck.fileUri, + fileUri: fileToCheck?.fileUri, localWorkspaceResolver: workspace => workspace.configPath.scheme === Schemas.file ? this.workspacesMainService.resolveLocalWorkspaceSync(workspace.configPath) : null }); @@ -615,10 +618,10 @@ export class WindowsManager extends Disposable implements IWindowsMainService { if (allWorkspacesToOpen.length > 0) { // Check for existing instances - const windowsOnWorkspace = arrays.coalesce(allWorkspacesToOpen.map(workspaceToOpen => findWindowOnWorkspace(WindowsManager.WINDOWS, workspaceToOpen.workspace))); + const windowsOnWorkspace = arrays.coalesce(allWorkspacesToOpen.map(workspaceToOpen => findWindowOnWorkspace(WindowsMainService.WINDOWS, workspaceToOpen.workspace))); if (windowsOnWorkspace.length > 0) { const windowOnWorkspace = windowsOnWorkspace[0]; - const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === windowOnWorkspace.remoteAuthority) ? fileInputs : undefined; + const fileInputsForWindow = (fileInputs?.remoteAuthority === windowOnWorkspace.remoteAuthority) ? fileInputs : undefined; // Do open files usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnWorkspace, fileInputsForWindow)); @@ -633,12 +636,12 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // Open remaining ones allWorkspacesToOpen.forEach(workspaceToOpen => { - if (windowsOnWorkspace.some(win => win.openedWorkspace!.id === workspaceToOpen.workspace.id)) { + if (windowsOnWorkspace.some(win => win.openedWorkspace && win.openedWorkspace.id === workspaceToOpen.workspace.id)) { return; // ignore folders that are already open } const remoteAuthority = workspaceToOpen.remoteAuthority; - const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : undefined; + const fileInputsForWindow = (fileInputs?.remoteAuthority === remoteAuthority) ? fileInputs : undefined; // Do open folder usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, workspaceToOpen, openFolderInNewWindow, fileInputsForWindow)); @@ -657,10 +660,10 @@ export class WindowsManager extends Disposable implements IWindowsMainService { if (allFoldersToOpen.length > 0) { // Check for existing instances - const windowsOnFolderPath = arrays.coalesce(allFoldersToOpen.map(folderToOpen => findWindowOnWorkspace(WindowsManager.WINDOWS, folderToOpen.folderUri))); + const windowsOnFolderPath = arrays.coalesce(allFoldersToOpen.map(folderToOpen => findWindowOnWorkspace(WindowsMainService.WINDOWS, folderToOpen.folderUri))); if (windowsOnFolderPath.length > 0) { const windowOnFolderPath = windowsOnFolderPath[0]; - const fileInputsForWindow = fileInputs && fileInputs.remoteAuthority === windowOnFolderPath.remoteAuthority ? fileInputs : undefined; + const fileInputsForWindow = fileInputs?.remoteAuthority === windowOnFolderPath.remoteAuthority ? fileInputs : undefined; // Do open files usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnFolderPath, fileInputsForWindow)); @@ -681,7 +684,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { } const remoteAuthority = folderToOpen.remoteAuthority; - const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : undefined; + const fileInputsForWindow = (fileInputs?.remoteAuthority === remoteAuthority) ? fileInputs : undefined; // Do open folder usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, folderToOpen, openFolderInNewWindow, fileInputsForWindow)); @@ -700,7 +703,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { if (allEmptyToRestore.length > 0) { allEmptyToRestore.forEach(emptyWindowBackupInfo => { const remoteAuthority = emptyWindowBackupInfo.remoteAuthority; - const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : undefined; + const fileInputsForWindow = (fileInputs?.remoteAuthority === remoteAuthority) ? fileInputs : undefined; usedWindows.push(this.openInBrowserWindow({ userEnv: openConfig.userEnv, @@ -963,12 +966,12 @@ export class WindowsManager extends Disposable implements IWindowsMainService { for (const openedWindow of openedWindows) { if (openedWindow.workspace) { // Workspaces const pathToOpen = this.parseUri({ workspaceUri: openedWindow.workspace.configPath }, { remoteAuthority: openedWindow.remoteAuthority }); - if (pathToOpen && pathToOpen.workspace) { + if (pathToOpen?.workspace) { windowsToOpen.push(pathToOpen); } } else if (openedWindow.folderUri) { // Folders const pathToOpen = this.parseUri({ folderUri: openedWindow.folderUri }, { remoteAuthority: openedWindow.remoteAuthority }); - if (pathToOpen && pathToOpen.folderUri) { + if (pathToOpen?.folderUri) { windowsToOpen.push(pathToOpen); } } else if (restoreWindows !== 'folders' && openedWindow.backupPath && !openedWindow.remoteAuthority) { // Local windows that were empty. Empty windows with backups will always be restored in open() @@ -993,7 +996,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { restoreWindows = 'all'; // always reopen all windows when an update was applied } else { const windowConfig = this.configurationService.getValue('window'); - restoreWindows = ((windowConfig && windowConfig.restoreWindows) || 'one'); + restoreWindows = windowConfig?.restoreWindows || 'one'; if (['all', 'folders', 'one', 'none'].indexOf(restoreWindows) === -1) { restoreWindows = 'one'; @@ -1170,7 +1173,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { this.workspacesHistoryMainService.removeFromRecentlyOpened([fileUri]); // since file does not seem to exist anymore, remove from recent // assume this is a file that does not yet exist - if (options && options.ignoreFileNotFound) { + if (options?.ignoreFileNotFound) { return { fileUri, remoteAuthority, @@ -1186,8 +1189,8 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // let the user settings override how folders are open in a new window or same window unless we are forced const windowConfig = this.configurationService.getValue('window'); - const openFolderInNewWindowConfig = (windowConfig && windowConfig.openFoldersInNewWindow) || 'default' /* default */; - const openFilesInNewWindowConfig = (windowConfig && windowConfig.openFilesInNewWindow) || 'off' /* default */; + const openFolderInNewWindowConfig = windowConfig?.openFoldersInNewWindow || 'default' /* default */; + const openFilesInNewWindowConfig = windowConfig?.openFilesInNewWindow || 'off' /* default */; let openFolderInNewWindow = (openConfig.preferNewWindow || openConfig.forceNewWindow) && !openConfig.forceReuseWindow; if (!openConfig.forceNewWindow && !openConfig.forceReuseWindow && (openFolderInNewWindowConfig === 'on' || openFolderInNewWindowConfig === 'off')) { @@ -1229,9 +1232,9 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // Reload an existing extension development host window on the same path // We currently do not allow more than one extension development window // on the same extension path. - const existingWindow = findWindowOnExtensionDevelopmentPath(WindowsManager.WINDOWS, extensionDevelopmentPath); + const existingWindow = findWindowOnExtensionDevelopmentPath(WindowsMainService.WINDOWS, extensionDevelopmentPath); if (existingWindow) { - this.reload(existingWindow, openConfig.cli); + this.lifecycleMainService.reload(existingWindow, openConfig.cli); existingWindow.focus(); // make sure it gets focus and is restored return [existingWindow]; @@ -1283,7 +1286,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { cliArgs = cliArgs.filter(path => { const uri = URI.file(path); - if (!!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, uri)) { + if (!!findWindowOnWorkspaceOrFolderUri(WindowsMainService.WINDOWS, uri)) { return false; } return uri.authority === authority; @@ -1291,7 +1294,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { folderUris = folderUris.filter(uri => { const u = this.argToUri(uri); - if (!!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, u)) { + if (!!findWindowOnWorkspaceOrFolderUri(WindowsMainService.WINDOWS, u)) { return false; } return u ? u.authority === authority : false; @@ -1299,7 +1302,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { fileUris = fileUris.filter(uri => { const u = this.argToUri(uri); - if (!!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, u)) { + if (!!findWindowOnWorkspaceOrFolderUri(WindowsMainService.WINDOWS, u)) { return false; } return u ? u.authority === authority : false; @@ -1375,12 +1378,21 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // Window state is not from a previous session: only allow fullscreen if we inherit it or user wants fullscreen let allowFullscreen: boolean; if (state.hasDefaultState) { - allowFullscreen = (windowConfig && windowConfig.newWindowDimensions && ['fullscreen', 'inherit'].indexOf(windowConfig.newWindowDimensions) >= 0); + allowFullscreen = (windowConfig?.newWindowDimensions && ['fullscreen', 'inherit'].indexOf(windowConfig.newWindowDimensions) >= 0); } // Window state is from a previous session: only allow fullscreen when we got updated or user wants to restore else { - allowFullscreen = this.lifecycleMainService.wasRestarted || (windowConfig && windowConfig.restoreFullscreen); + allowFullscreen = this.lifecycleMainService.wasRestarted || windowConfig?.restoreFullscreen; + + if (allowFullscreen && isMacintosh && WindowsMainService.WINDOWS.some(win => win.isFullScreen)) { + // macOS: Electron does not allow to restore multiple windows in + // fullscreen. As such, if we already restored a window in that + // state, we cannot allow more fullscreen windows. See + // https://github.com/microsoft/vscode/issues/41691 and + // https://github.com/electron/electron/issues/13077 + allowFullscreen = false; + } } if (state.mode === WindowMode.Fullscreen && !allowFullscreen) { @@ -1388,7 +1400,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { } // Create the window - window = this.instantiationService.createInstance(CodeWindow, { + const createdWindow = window = this.instantiationService.createInstance(CodeWindow, { state, extensionDevelopmentPath: configuration.extensionDevelopmentPath, isExtensionTestHost: !!configuration.extensionTestsPath @@ -1403,17 +1415,16 @@ export class WindowsManager extends Disposable implements IWindowsMainService { } // Add to our list of windows - WindowsManager.WINDOWS.push(window); + WindowsMainService.WINDOWS.push(window); // Indicate number change via event - this._onWindowsCountChanged.fire({ oldCount: WindowsManager.WINDOWS.length - 1, newCount: WindowsManager.WINDOWS.length }); + this._onWindowsCountChanged.fire({ oldCount: WindowsMainService.WINDOWS.length - 1, newCount: WindowsMainService.WINDOWS.length }); // Window Events + once(window.onClose)(() => this.onWindowClosed(createdWindow)); + once(window.onDestroy)(() => this.onBeforeWindowClose(createdWindow)); // try to save state before destroy because close will not fire window.win.webContents.removeAllListeners('devtools-reload-page'); // remove built in listener so we can handle this on our own - window.win.webContents.on('devtools-reload-page', () => this.reload(window!)); - window.win.webContents.on('crashed', () => this.onWindowError(window!, WindowError.CRASHED)); - window.win.on('unresponsive', () => this.onWindowError(window!, WindowError.UNRESPONSIVE)); - window.win.on('closed', () => this.onWindowClosed(window!)); + window.win.webContents.on('devtools-reload-page', () => this.lifecycleMainService.reload(createdWindow)); // Lifecycle (this.lifecycleMainService as LifecycleMainService).registerWindow(window); @@ -1467,9 +1478,6 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // Load it window.load(configuration); - - // Signal event - this._onWindowLoad.fire(window.id); } private getNewWindowState(configuration: IWindowConfiguration): INewWindowState { @@ -1558,7 +1566,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // Check for newWindowDimensions setting and adjust accordingly const windowConfig = this.configurationService.getValue('window'); let ensureNoOverlap = true; - if (windowConfig && windowConfig.newWindowDimensions) { + if (windowConfig?.newWindowDimensions) { if (windowConfig.newWindowDimensions === 'maximized') { state.mode = WindowMode.Maximized; ensureNoOverlap = false; @@ -1587,14 +1595,14 @@ export class WindowsManager extends Disposable implements IWindowsMainService { } private ensureNoOverlap(state: ISingleWindowState): ISingleWindowState { - if (WindowsManager.WINDOWS.length === 0) { + if (WindowsMainService.WINDOWS.length === 0) { return state; } state.x = typeof state.x === 'number' ? state.x : 0; state.y = typeof state.y === 'number' ? state.y : 0; - const existingWindowBounds = WindowsManager.WINDOWS.map(win => win.getBounds()); + const existingWindowBounds = WindowsMainService.WINDOWS.map(win => win.getBounds()); while (existingWindowBounds.some(b => b.x === state.x || b.y === state.y)) { state.x += 30; state.y += 30; @@ -1603,23 +1611,6 @@ export class WindowsManager extends Disposable implements IWindowsMainService { return state; } - async reload(win: ICodeWindow, cli?: ParsedArgs): Promise { - - // Only reload when the window has not vetoed this - const veto = await this.lifecycleMainService.unload(win, UnloadReason.RELOAD); - if (!veto) { - win.reload(undefined, cli); - } - } - - closeWorkspace(win: ICodeWindow): void { - this.openInBrowserWindow({ - cli: this.environmentService.args, - windowToUse: win, - remoteAuthority: win.remoteAuthority - }); - } - focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow { const lastActive = this.getLastActiveWindow(); if (lastActive) { @@ -1633,40 +1624,11 @@ export class WindowsManager extends Disposable implements IWindowsMainService { } getLastActiveWindow(): ICodeWindow | undefined { - return getLastActiveWindow(WindowsManager.WINDOWS); + return getLastActiveWindow(WindowsMainService.WINDOWS); } private getLastActiveWindowForAuthority(remoteAuthority: string | undefined): ICodeWindow | undefined { - return getLastActiveWindow(WindowsManager.WINDOWS.filter(window => window.remoteAuthority === remoteAuthority)); - } - - openEmptyWindow(context: OpenContext, options?: IOpenEmptyWindowOptions): ICodeWindow[] { - let cli = this.environmentService.args; - const remote = options && options.remoteAuthority; - if (cli && (cli.remote !== remote)) { - cli = { ...cli, remote }; - } - - const forceReuseWindow = options && options.forceReuseWindow; - const forceNewWindow = !forceReuseWindow; - - return this.open({ context, cli, forceEmpty: true, forceNewWindow, forceReuseWindow }); - } - - waitForWindowCloseOrLoad(windowId: number): Promise { - return new Promise(resolve => { - function handler(id: number) { - if (id === windowId) { - closeListener.dispose(); - loadListener.dispose(); - - resolve(); - } - } - - const closeListener = this.onWindowClose(id => handler(id)); - const loadListener = this.onWindowLoad(id => handler(id)); - }); + return getLastActiveWindow(WindowsMainService.WINDOWS.filter(window => window.remoteAuthority === remoteAuthority)); } sendToFocused(channel: string, ...args: any[]): void { @@ -1678,7 +1640,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { } sendToAll(channel: string, payload?: any, windowIdsToIgnore?: number[]): void { - for (const window of WindowsManager.WINDOWS) { + for (const window of WindowsMainService.WINDOWS) { if (windowIdsToIgnore && windowIdsToIgnore.indexOf(window.id) >= 0) { continue; // do not send if we are instructed to ignore it } @@ -1697,187 +1659,27 @@ export class WindowsManager extends Disposable implements IWindowsMainService { } getWindowById(windowId: number): ICodeWindow | undefined { - const res = WindowsManager.WINDOWS.filter(window => window.id === windowId); + const res = WindowsMainService.WINDOWS.filter(window => window.id === windowId); + return arrays.firstOrDefault(res); } getWindows(): ICodeWindow[] { - return WindowsManager.WINDOWS; + return WindowsMainService.WINDOWS; } getWindowCount(): number { - return WindowsManager.WINDOWS.length; - } - - private onWindowError(window: ICodeWindow, error: WindowError): void { - this.logService.error(error === WindowError.CRASHED ? '[VS Code]: render process crashed!' : '[VS Code]: detected unresponsive'); - type WindowErrorClassification = { - type: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true }; - }; - type WindowErrorEvent = { - type: WindowError; - }; - this.telemetryService.publicLog2('windowerror', { type: error }); - // Unresponsive - if (error === WindowError.UNRESPONSIVE) { - if (window.isExtensionDevelopmentHost || window.isExtensionTestHost || (window.win && window.win.webContents && window.win.webContents.isDevToolsOpened())) { - // TODO@Ben Workaround for https://github.com/Microsoft/vscode/issues/56994 - // In certain cases the window can report unresponsiveness because a breakpoint was hit - // and the process is stopped executing. The most typical cases are: - // - devtools are opened and debugging happens - // - window is an extensions development host that is being debugged - // - window is an extension test development host that is being debugged - return; - } - - // Show Dialog - this.dialogMainService.showMessageBox({ - title: product.nameLong, - type: 'warning', - buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'wait', comment: ['&& denotes a mnemonic'] }, "&&Keep Waiting")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))], - message: localize('appStalled', "The window is no longer responding"), - detail: localize('appStalledDetail', "You can reopen or close the window or keep waiting."), - noLink: true - }, window.win).then(result => { - if (!window.win) { - return; // Return early if the window has been going down already - } - - if (result.response === 0) { - window.reload(); - } else if (result.response === 2) { - this.onBeforeWindowClose(window); // 'close' event will not be fired on destroy(), so run it manually - window.win.destroy(); // make sure to destroy the window as it is unresponsive - } - }); - } - - // Crashed - else { - this.dialogMainService.showMessageBox({ - title: product.nameLong, - type: 'warning', - buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))], - message: localize('appCrashed', "The window has crashed"), - detail: localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."), - noLink: true - }, window.win).then(result => { - if (!window.win) { - return; // Return early if the window has been going down already - } - - if (result.response === 0) { - window.reload(); - } else if (result.response === 1) { - this.onBeforeWindowClose(window); // 'close' event will not be fired on destroy(), so run it manually - window.win.destroy(); // make sure to destroy the window as it has crashed - } - }); - } + return WindowsMainService.WINDOWS.length; } private onWindowClosed(win: ICodeWindow): void { - // Tell window - win.dispose(); - // Remove from our list so that Electron can clean it up - const index = WindowsManager.WINDOWS.indexOf(win); - WindowsManager.WINDOWS.splice(index, 1); + const index = WindowsMainService.WINDOWS.indexOf(win); + WindowsMainService.WINDOWS.splice(index, 1); // Emit - this._onWindowsCountChanged.fire({ oldCount: WindowsManager.WINDOWS.length + 1, newCount: WindowsManager.WINDOWS.length }); + this._onWindowsCountChanged.fire({ oldCount: WindowsMainService.WINDOWS.length + 1, newCount: WindowsMainService.WINDOWS.length }); this._onWindowClose.fire(win.id); } - - async pickFileFolderAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise { - const paths = await this.dialogMainService.pickFileFolder(options); - if (paths) { - this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFileFolder', options.telemetryExtraData); - 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, - contextWindowId: win ? win.id : undefined, - cli: this.environmentService.args, - urisToOpen, - forceNewWindow: options.forceNewWindow - }); - } - } - - async pickFolderAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise { - const paths = await this.dialogMainService.pickFolder(options); - if (paths) { - this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFolder', options.telemetryExtraData); - this.open({ - context: OpenContext.DIALOG, - contextWindowId: win ? win.id : undefined, - cli: this.environmentService.args, - urisToOpen: paths.map(path => ({ folderUri: URI.file(path) })), - forceNewWindow: options.forceNewWindow - }); - } - } - - async pickFileAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise { - const paths = await this.dialogMainService.pickFile(options); - if (paths) { - this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFile', options.telemetryExtraData); - this.open({ - context: OpenContext.DIALOG, - contextWindowId: win ? win.id : undefined, - cli: this.environmentService.args, - urisToOpen: paths.map(path => ({ fileUri: URI.file(path) })), - forceNewWindow: options.forceNewWindow - }); - } - } - - async pickWorkspaceAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise { - const paths = await this.dialogMainService.pickWorkspace(options); - if (paths) { - this.sendPickerTelemetry(paths, options.telemetryEventName || 'openWorkspace', options.telemetryExtraData); - this.open({ - context: OpenContext.DIALOG, - contextWindowId: win ? win.id : undefined, - cli: this.environmentService.args, - urisToOpen: paths.map(path => ({ workspaceUri: URI.file(path) })), - forceNewWindow: options.forceNewWindow - }); - } - - } - - private sendPickerTelemetry(paths: string[], telemetryEventName: string, telemetryExtraData?: ITelemetryData) { - const numberOfPaths = paths ? paths.length : 0; - - // Telemetry - // __GDPR__TODO__ Dynamic event names and dynamic properties. Can not be registered statically. - this.telemetryService.publicLog(telemetryEventName, { - ...telemetryExtraData, - outcome: numberOfPaths ? 'success' : 'canceled', - numberOfPaths - }); - } - - quit(): void { - - // If the user selected to exit from an extension development host window, do not quit, but just - // close the window unless this is the last window that is opened. - const window = this.getFocusedWindow(); - if (window && window.isExtensionDevelopmentHost && this.getWindowCount() > 1) { - window.win.close(); - } - - // Otherwise: normal quit - else { - setTimeout(() => { - this.lifecycleMainService.quit(); - }, 10 /* delay to unwind callback stack (IPC) */); - } - } } diff --git a/src/vs/code/electron-main/windowsStateStorage.ts b/src/vs/platform/windows/electron-main/windowsStateStorage.ts similarity index 97% rename from src/vs/code/electron-main/windowsStateStorage.ts rename to src/vs/platform/windows/electron-main/windowsStateStorage.ts index ef65864434..992f6f2a09 100644 --- a/src/vs/code/electron-main/windowsStateStorage.ts +++ b/src/vs/platform/windows/electron-main/windowsStateStorage.ts @@ -5,7 +5,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { IWindowState as IWindowUIState } from 'vs/platform/windows/electron-main/windows'; -import { IWindowState, IWindowsState } from 'vs/code/electron-main/windows'; +import { IWindowState, IWindowsState } from 'vs/platform/windows/electron-main/windowsMainService'; export type WindowsStateStorageData = object; diff --git a/src/vs/platform/windows/node/window.ts b/src/vs/platform/windows/node/window.ts index 4765a18188..a0b4eb75e4 100644 --- a/src/vs/platform/windows/node/window.ts +++ b/src/vs/platform/windows/node/window.ts @@ -111,7 +111,7 @@ export function findWindowOnExtensionDevelopmentPath(w for (const window of windows) { // match on extension development path. The path can be one or more paths or uri strings, using paths.isEqual is not 100% correct but good enough const currPaths = window.extensionDevelopmentPath; - if (currPaths && currPaths.some(p => matches(p))) { + if (currPaths?.some(p => matches(p))) { return window; } } diff --git a/src/vs/platform/workspaces/common/workspaces.ts b/src/vs/platform/workspaces/common/workspaces.ts index 08c02ae2ce..9a665730a8 100644 --- a/src/vs/platform/workspaces/common/workspaces.ts +++ b/src/vs/platform/workspaces/common/workspaces.ts @@ -294,12 +294,7 @@ function doParseStoredWorkspace(path: URI, contents: string): IStoredWorkspace { export function useSlashForPath(storedFolders: IStoredWorkspaceFolder[]): boolean { if (isWindows) { - for (const folder of storedFolders) { - if (isRawFileWorkspaceFolder(folder) && folder.path.indexOf(SLASH) >= 0) { - return true; - } - } - return false; + return storedFolders.some(folder => isRawFileWorkspaceFolder(folder) && folder.path.indexOf(SLASH) >= 0); } return true; } diff --git a/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts index c90f13ae4c..151a655ba0 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts @@ -8,7 +8,7 @@ import * as arrays from 'vs/base/common/arrays'; import { IStateService } from 'vs/platform/state/node/state'; import { app, JumpListCategory } from 'electron'; import { ILogService } from 'vs/platform/log/common/log'; -import { getBaseLabel, getPathLabel } from 'vs/base/common/labels'; +import { getBaseLabel, getPathLabel, splitName } from 'vs/base/common/labels'; import { IPath } from 'vs/platform/windows/common/windows'; import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; import { isWindows, isMacintosh } from 'vs/base/common/platform'; @@ -352,7 +352,7 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa name: nls.localize('recentFolders', "Recent Workspaces"), items: arrays.coalesce(this.getRecentlyOpened().workspaces.slice(0, 7 /* limit number of entries here */).map(recent => { const workspace = isRecentWorkspace(recent) ? recent.workspace : recent.folderUri; - const title = recent.label || getSimpleWorkspaceLabel(workspace, this.environmentService.untitledWorkspacesHome); + const title = recent.label ? splitName(recent.label).name : getSimpleWorkspaceLabel(workspace, this.environmentService.untitledWorkspacesHome); let description; let args; diff --git a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts index c2ed8d7ba3..bc4dc061b6 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts @@ -247,7 +247,7 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain } } } catch (error) { - if (error && error.code !== 'ENOENT') { + if (error.code !== 'ENOENT') { this.logService.warn(`Unable to read folders in ${this.untitledWorkspacesHome} (${error}).`); } } @@ -265,6 +265,9 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain } const result = this.doEnterWorkspace(window, getWorkspaceIdentifier(path)); + if (!result) { + return null; + } // Emit as event this._onWorkspaceEntered.fire({ window, workspace: result.workspace }); @@ -300,7 +303,11 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain return true; // OK } - private doEnterWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult { + private doEnterWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult | null { + if (!window.config) { + return null; + } + window.focus(); // Register window for backups and migrate current backups over diff --git a/src/vs/platform/workspaces/electron-main/workspacesService.ts b/src/vs/platform/workspaces/electron-main/workspacesService.ts index b1afbbbcb7..6dd1c1fb95 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesService.ts @@ -52,7 +52,7 @@ export class WorkspacesService implements AddFirstParameterToFunctions { const window = this.windowsMainService.getWindowById(windowId); - if (window) { + if (window?.config) { return this.workspacesHistoryMainService.getRecentlyOpened(window.config.workspace, window.config.folderUri, window.config.filesToOpenOrCreate); } diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index f2f92bca7d..d0e070a0a4 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -5222,7 +5222,7 @@ declare module 'vscode' { /** * The shell command line. Is `undefined` if created with a command and arguments. */ - commandLine: string; + commandLine: string | undefined; /** * The shell command. Is `undefined` if created with a full command line. @@ -6177,6 +6177,22 @@ declare module 'vscode' { writeText(value: string): Thenable; } + /** + * Possible kinds of UI that can use extensions. + */ + export enum UIKind { + + /** + * Extensions are accessed from a desktop application. + */ + Desktop = 1, + + /** + * Extensions are accessed from a web browser. + */ + Web = 2 + } + /** * Namespace describing the environment the editor runs in. */ @@ -6235,6 +6251,13 @@ declare module 'vscode' { */ export const shell: string; + /** + * The UI kind property indicates from which UI extensions + * are accessed from. For example, extensions could be accessed + * from a desktop application or a web browser. + */ + export const uiKind: UIKind; + /** * Opens an *external* item, e.g. a http(s) or mailto-link, using the * default application. @@ -6246,6 +6269,28 @@ declare module 'vscode' { * @returns A promise indicating if open was successful. */ export function openExternal(target: Uri): Thenable; + + /** + * Resolves an *external* uri, such as a `http:` or `https:` link, from where the extension is running to a + * uri to the same resource on the client machine. + * + * This is a no-op if the extension is running on the client machine. Currently only supports + * `https:` and `http:` uris. + * + * If the extension is running remotely, this function automatically establishes a port forwarding tunnel + * from the local machine to `target` on the remote and returns a local uri to the tunnel. The lifetime of + * the port fowarding tunnel is managed by VS Code and the tunnel can be closed by the user. + * + * Extensions should not cache the result of `asExternalUri` as the resolved uri may become invalid due to + * a system or user action — for example, in remote cases, a user may close a port forwardng tunnel + * that was opened by `asExternalUri`. + * + * Note: uris passed through `openExternal` are automatically resolved and you should not call `asExternalUri` + * on them. + * + * @return A uri that can be used on the client machine. + */ + export function asExternalUri(target: Uri): Thenable; } /** @@ -6333,7 +6378,7 @@ declare module 'vscode' { export function executeCommand(command: string, ...rest: any[]): Thenable; /** - * Retrieve the list of all available commands. Commands starting an underscore are + * Retrieve the list of all available commands. Commands starting with an underscore are * treated as internal commands. * * @param filterInternal Set `true` to not see internal commands (starting with an underscore) @@ -7016,6 +7061,7 @@ declare module 'vscode' { /** * An optional human-readable message that will be rendered in the view. + * Setting the message to null, undefined, or empty string will remove the message from the view. */ message?: string; @@ -7297,7 +7343,7 @@ declare module 'vscode' { * A number can be used to provide an exit code for the terminal. Exit codes must be * positive and a non-zero exit codes signals failure which shows a notification for a * regular terminal and allows dependent tasks to proceed when used with the - * `CustomExecution2` API. + * `CustomExecution` API. * * **Example:** Exit the terminal when "y" is pressed, otherwise show a notification. * ```typescript @@ -9307,10 +9353,10 @@ declare module 'vscode' { * Folder specific variables used in the configuration (e.g. '${workspaceFolder}') are resolved against the given folder. * @param folder The [workspace folder](#WorkspaceFolder) for looking up named configurations and resolving variables or `undefined` for a non-folder setup. * @param nameOrConfiguration Either the name of a debug or compound configuration or a [DebugConfiguration](#DebugConfiguration) object. - * @param parent If specified the newly created debug session is registered as a "child" session of a "parent" debug session. + * @param parentSessionOrOptions Debug sesison options. When passed a parent [debug session](#DebugSession), assumes options with just this parent session. * @return A thenable that resolves when debugging could be successfully started. */ - export function startDebugging(folder: WorkspaceFolder | undefined, nameOrConfiguration: string | DebugConfiguration, parentSession?: DebugSession): Thenable; + export function startDebugging(folder: WorkspaceFolder | undefined, nameOrConfiguration: string | DebugConfiguration, parentSessionOrOptions?: DebugSession | DebugSessionOptions): Thenable; /** * Add breakpoints. diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 1d620dd34b..27859e238c 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -59,15 +59,15 @@ declare module 'vscode' { } export class CallHierarchyIncomingCall { - source: CallHierarchyItem; - sourceRanges: Range[]; - constructor(item: CallHierarchyItem, sourceRanges: Range[]); + from: CallHierarchyItem; + fromRanges: Range[]; + constructor(item: CallHierarchyItem, fromRanges: Range[]); } export class CallHierarchyOutgoingCall { - sourceRanges: Range[]; - target: CallHierarchyItem; - constructor(item: CallHierarchyItem, sourceRanges: Range[]); + fromRanges: Range[]; + to: CallHierarchyItem; + constructor(item: CallHierarchyItem, fromRanges: Range[]); } export interface CallHierarchyItemProvider { @@ -651,21 +651,6 @@ declare module 'vscode' { consoleMode?: DebugConsoleMode; } - export namespace debug { - /** - * Start debugging by using either a named launch or named compound configuration, - * or by directly passing a [DebugConfiguration](#DebugConfiguration). - * The named configurations are looked up in '.vscode/launch.json' found in the given folder. - * Before debugging starts, all unsaved files are saved and the launch configurations are brought up-to-date. - * Folder specific variables used in the configuration (e.g. '${workspaceFolder}') are resolved against the given folder. - * @param folder The [workspace folder](#WorkspaceFolder) for looking up named configurations and resolving variables or `undefined` for a non-folder setup. - * @param nameOrConfiguration Either the name of a debug or compound configuration or a [DebugConfiguration](#DebugConfiguration) object. - * @param parentSessionOrOptions Debug sesison options. When passed a parent [debug session](#DebugSession), assumes options with just this parent session. - * @return A thenable that resolves when debugging could be successfully started. - */ - export function startDebugging(folder: WorkspaceFolder | undefined, nameOrConfiguration: string | DebugConfiguration, parentSessionOrOptions?: DebugSession | DebugSessionOptions): Thenable; - } - //#endregion //#region Rob, Matt: logging @@ -871,7 +856,8 @@ declare module 'vscode' { export interface TreeView { /** - * The name of the tree view. It is set from the extension package.json and can be changed later. + * The tree view title is initially taken from the extension package.json + * Changes to the title property will be properly reflected in the UI in the title of the view. */ title?: string; } @@ -912,7 +898,7 @@ declare module 'vscode' { /** * Class used to execute an extension callback as a task. */ - export class CustomExecution2 { + export class CustomExecution { /** * Constructs a CustomExecution task object. The callback will be executed the task is run, at which point the * extension should return the Pseudoterminal it will "run in". The task should wait to do further execution until @@ -941,12 +927,12 @@ declare module 'vscode' { * or '$eslint'. Problem matchers can be contributed by an extension using * the `problemMatchers` extension point. */ - constructor(taskDefinition: TaskDefinition, scope: WorkspaceFolder | TaskScope.Global | TaskScope.Workspace, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution2, problemMatchers?: string | string[]); + constructor(taskDefinition: TaskDefinition, scope: WorkspaceFolder | TaskScope.Global | TaskScope.Workspace, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution, problemMatchers?: string | string[]); /** * The task's execution engine */ - execution2?: ProcessExecution | ShellExecution | CustomExecution2; + execution2?: ProcessExecution | ShellExecution | CustomExecution; } //#endregion @@ -1053,36 +1039,6 @@ declare module 'vscode' { //#endregion - // #region Ben - UIKind - - /** - * Possible kinds of UI that can use extensions. - */ - export enum UIKind { - - /** - * Extensions are accessed from a desktop application. - */ - Desktop = 1, - - /** - * Extensions are accessed from a web browser. - */ - Web = 2 - } - - export namespace env { - - /** - * The UI kind property indicates from which UI extensions - * are accessed from. For example, extensions could be accessed - * from a desktop application or a web browser. - */ - export const uiKind: UIKind; - } - - //#endregion - //#region Custom editors, mjbvz export interface WebviewEditor extends WebviewPanel { @@ -1104,34 +1060,9 @@ declare module 'vscode' { export function registerWebviewEditorProvider( viewType: string, provider: WebviewEditorProvider, + options?: WebviewPanelOptions ): Disposable; } //#endregion - - // #region resolveExternalUri — mjbvz - - namespace env { - /** - * Resolves an *external* uri, such as a `http:` or `https:` link, from where the extension is running to a - * uri to the same resource on the client machine. - * - * This is a no-op if the extension is running locally. Currently only supports `https:` and `http:`. - * - * If the extension is running remotely, this function automatically establishes port forwarding from - * the local machine to `target` on the remote and returns a local uri that can be used to for this connection. - * - * Extensions should not store the result of `resolveExternalUri` as the resolved uri may become invalid due to - * a system or user action — for example, in remote cases, a user may close a port that was forwarded by - * `resolveExternalUri`. - * - * Note: uris passed through `openExternal` are automatically resolved and you should not call `resolveExternalUri` - * on them. - * - * @return A uri that can be used on the client machine. - */ - export function asExternalUri(target: Uri): Thenable; - } - - //#endregion } diff --git a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts index 9c755dbe3d..c3cad12a5d 100644 --- a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts +++ b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts @@ -12,6 +12,7 @@ import { IWebviewService, WebviewElement } from 'vs/workbench/contrib/webview/br import { DisposableStore } from 'vs/base/common/lifecycle'; import { IActiveCodeEditor, IViewZone } from 'vs/editor/browser/editorBrowser'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { isEqual } from 'vs/base/common/resources'; // todo@joh move these things back into something like contrib/insets class EditorWebviewZone implements IViewZone { @@ -75,7 +76,7 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape { 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()) { + if (candidate.getId() === id && candidate.hasModel() && isEqual(candidate.getModel().uri, URI.revive(uri))) { editor = candidate; break; } diff --git a/src/vs/workbench/api/browser/mainThreadEditor.ts b/src/vs/workbench/api/browser/mainThreadEditor.ts index 1e984c156c..929bc8f9ca 100644 --- a/src/vs/workbench/api/browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/browser/mainThreadEditor.ts @@ -16,6 +16,7 @@ import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2 import { IApplyEditsOptions, IEditorPropertiesChangeData, IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate, IUndoStopOptions, TextEditorRevealType } from 'vs/workbench/api/common/extHost.protocol'; import { IEditor } from 'vs/workbench/common/editor'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { equals } from 'vs/base/common/arrays'; export interface IFocusTracker { onGainedFocus(): void; @@ -124,28 +125,12 @@ export class MainThreadTextEditorProperties { return null; } - private static _selectionsEqual(a: Selection[], b: Selection[]): boolean { - if (a.length !== b.length) { - return false; - } - for (let i = 0; i < a.length; i++) { - if (!a[i].equalsSelection(b[i])) { - return false; - } - } - return true; + private static _selectionsEqual(a: readonly Selection[], b: readonly Selection[]): boolean { + return equals(a, b, (aValue, bValue) => aValue.equalsSelection(bValue)); } - private static _rangesEqual(a: Range[], b: Range[]): boolean { - if (a.length !== b.length) { - return false; - } - for (let i = 0; i < a.length; i++) { - if (!a[i].equalsRange(b[i])) { - return false; - } - } - return true; + private static _rangesEqual(a: readonly Range[], b: readonly Range[]): boolean { + return equals(a, b, (aValue, bValue) => aValue.equalsRange(bValue)); } private static _optionsEqual(a: IResolvedTextEditorConfiguration, b: IResolvedTextEditorConfiguration): boolean { diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 3f8801787d..c1ca1167d4 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -500,10 +500,10 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha if (!outgoing) { return undefined; // {{SQL CARBON EDIT}} strict-null-check } - return outgoing.map(([item, sourceRanges]): callh.OutgoingCall => { + return outgoing.map(([item, fromRanges]): callh.OutgoingCall => { return { - target: MainThreadLanguageFeatures._reviveCallHierarchyItemDto(item), - sourceRanges + to: MainThreadLanguageFeatures._reviveCallHierarchyItemDto(item), + fromRanges }; }); }, @@ -512,10 +512,10 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha if (!incoming) { return undefined; // {{SQL CARBON EDIT}} strict-null-check } - return incoming.map(([item, sourceRanges]): callh.IncomingCall => { + return incoming.map(([item, fromRanges]): callh.IncomingCall => { return { - source: MainThreadLanguageFeatures._reviveCallHierarchyItemDto(item), - sourceRanges + from: MainThreadLanguageFeatures._reviveCallHierarchyItemDto(item), + fromRanges }; }); } diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 1047fabd35..f69951857c 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -114,7 +114,7 @@ class MainThreadSCMProvider implements ISCMProvider { get rootUri(): URI | undefined { return this._rootUri; } get contextValue(): string { return this._contextValue; } - get commitTemplate(): string | undefined { return this.features.commitTemplate; } + get commitTemplate(): string { return this.features.commitTemplate || ''; } get acceptInputCommand(): Command | undefined { return this.features.acceptInputCommand; } get statusBarCommands(): Command[] | undefined { return this.features.statusBarCommands; } get count(): number | undefined { return this.features.count; } diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index 00bdcbdf4a..142c801299 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -75,7 +75,7 @@ class TrimWhitespaceParticipant implements ISaveParticipantParticipant { // Nothing } - async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise { + async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { if (this.configurationService.getValue('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) { this.doTrimTrailingWhitespace(model.textEditorModel, env.reason === SaveReason.AUTO); } @@ -137,7 +137,7 @@ export class FinalNewLineParticipant implements ISaveParticipantParticipant { // Nothing } - async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise { + async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { if (this.configurationService.getValue('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) { this.doInsertFinalNewLine(model.textEditorModel); } @@ -171,7 +171,7 @@ export class TrimFinalNewLinesParticipant implements ISaveParticipantParticipant // Nothing } - async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise { + async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { if (this.configurationService.getValue('files.trimFinalNewlines', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) { this.doTrimFinalNewLines(model.textEditorModel, env.reason === SaveReason.AUTO); } @@ -241,7 +241,7 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant { // Nothing } - async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise { + async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { const model = editorModel.textEditorModel; const overrides = { overrideIdentifier: model.getLanguageIdentifier().language, resource: model.uri }; @@ -275,7 +275,7 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { } - async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise { + async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { if (env.reason === SaveReason.AUTO) { return undefined; } @@ -358,7 +358,7 @@ class ExtHostSaveParticipant implements ISaveParticipantParticipant { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentSaveParticipant); } - async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise { + async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { if (!shouldSynchronizeModel(editorModel.textEditorModel)) { // the model never made it to the extension @@ -369,10 +369,8 @@ class ExtHostSaveParticipant implements ISaveParticipantParticipant { return new Promise((resolve, reject) => { setTimeout(() => reject(localize('timeout.onWillSave', "Aborted onWillSaveTextDocument-event after 1750ms")), 1750); this._proxy.$participateInSave(editorModel.getResource(), env.reason).then(values => { - for (const success of values) { - if (!success) { - return Promise.reject(new Error('listener failed')); - } + if (!values.every(success => success)) { + return Promise.reject(new Error('listener failed')); } return undefined; }).then(resolve, reject); @@ -411,7 +409,7 @@ export class SaveParticipant implements ISaveParticipant { this._saveParticipants.dispose(); } - async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise { + async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { return this._progressService.withProgress({ location: ProgressLocation.Window }, progress => { progress.report({ message: localize('saveParticipants', "Running Save Participants...") }); const promiseFactory = this._saveParticipants.getValue().map(p => () => { diff --git a/src/vs/workbench/api/browser/mainThreadStatusBar.ts b/src/vs/workbench/api/browser/mainThreadStatusBar.ts index dd873437cc..9a5a5cb919 100644 --- a/src/vs/workbench/api/browser/mainThreadStatusBar.ts +++ b/src/vs/workbench/api/browser/mainThreadStatusBar.ts @@ -24,9 +24,13 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape { this.entries.clear(); } - $setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string, command: string, color: string | ThemeColor, alignment: MainThreadStatusBarAlignment, priority: number): void { + $setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string | undefined, command: string | undefined, color: string | ThemeColor | undefined, alignment: MainThreadStatusBarAlignment, priority: number | undefined): void { const entry: IStatusbarEntry = { text, tooltip, command, color }; + if (typeof priority === 'undefined') { + priority = 0; + } + // Reset existing entry if alignment or priority changed let existingEntry = this.entries.get(id); if (existingEntry && (existingEntry.alignment !== alignment || existingEntry.priority !== priority)) { diff --git a/src/vs/workbench/api/browser/mainThreadTask.ts b/src/vs/workbench/api/browser/mainThreadTask.ts index 55f7a21065..fd26e7cb99 100644 --- a/src/vs/workbench/api/browser/mainThreadTask.ts +++ b/src/vs/workbench/api/browser/mainThreadTask.ts @@ -29,7 +29,7 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { ExtHostContext, MainThreadTaskShape, ExtHostTaskShape, MainContext, IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { TaskDefinitionDTO, TaskExecutionDTO, ProcessExecutionOptionsDTO, TaskPresentationOptionsDTO, - ProcessExecutionDTO, ShellExecutionDTO, ShellExecutionOptionsDTO, CustomExecution2DTO, TaskDTO, TaskSourceDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO, + ProcessExecutionDTO, ShellExecutionDTO, ShellExecutionOptionsDTO, CustomExecutionDTO, TaskDTO, TaskSourceDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO, RunOptionsDTO } from 'vs/workbench/api/common/shared/tasks'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; @@ -131,7 +131,7 @@ namespace ProcessExecutionOptionsDTO { } namespace ProcessExecutionDTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecution2DTO): value is ProcessExecutionDTO { + export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO): value is ProcessExecutionDTO { const candidate = value as ProcessExecutionDTO; return candidate && !!candidate.process; } @@ -199,7 +199,7 @@ namespace ShellExecutionOptionsDTO { } namespace ShellExecutionDTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecution2DTO): value is ShellExecutionDTO { + export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO): value is ShellExecutionDTO { const candidate = value as ShellExecutionDTO; return candidate && (!!candidate.commandLine || !!candidate.command); } @@ -230,21 +230,21 @@ namespace ShellExecutionDTO { } } -namespace CustomExecution2DTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecution2DTO): value is CustomExecution2DTO { - const candidate = value as CustomExecution2DTO; - return candidate && candidate.customExecution === 'customExecution2'; +namespace CustomExecutionDTO { + export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO): value is CustomExecutionDTO { + const candidate = value as CustomExecutionDTO; + return candidate && candidate.customExecution === 'customExecution'; } - export function from(value: CommandConfiguration): CustomExecution2DTO { + export function from(value: CommandConfiguration): CustomExecutionDTO { return { - customExecution: 'customExecution2' + customExecution: 'customExecution' }; } - export function to(value: CustomExecution2DTO): CommandConfiguration { + export function to(value: CustomExecutionDTO): CommandConfiguration { return { - runtime: RuntimeType.CustomExecution2, + runtime: RuntimeType.CustomExecution, presentation: undefined }; } @@ -264,7 +264,7 @@ namespace TaskSourceDTO { } } else if (value.kind === TaskSourceKind.Workspace) { result.extensionId = '$core'; - result.scope = value.config.workspaceFolder.uri; + result.scope = value.config.workspaceFolder ? value.config.workspaceFolder.uri : TaskScope.Global; } return result; } @@ -311,7 +311,7 @@ namespace TaskDTO { const result: TaskDTO = { _id: task._id, name: task.configurationProperties.name, - definition: TaskDefinitionDTO.from(task.getDefinition()), + definition: TaskDefinitionDTO.from(task.getDefinition(true)), source: TaskSourceDTO.from(task._source), execution: undefined, presentationOptions: !ConfiguringTask.is(task) && task.command ? TaskPresentationOptionsDTO.from(task.command.presentation) : undefined, @@ -351,8 +351,8 @@ namespace TaskDTO { command = ShellExecutionDTO.to(task.execution); } else if (ProcessExecutionDTO.is(task.execution)) { command = ProcessExecutionDTO.to(task.execution); - } else if (CustomExecution2DTO.is(task.execution)) { - command = CustomExecution2DTO.to(task.execution); + } else if (CustomExecutionDTO.is(task.execution)) { + command = CustomExecutionDTO.to(task.execution); } } diff --git a/src/vs/workbench/api/browser/mainThreadUrls.ts b/src/vs/workbench/api/browser/mainThreadUrls.ts index 5c4dccd26a..110b03fb5d 100644 --- a/src/vs/workbench/api/browser/mainThreadUrls.ts +++ b/src/vs/workbench/api/browser/mainThreadUrls.ts @@ -5,7 +5,7 @@ import { ExtHostContext, IExtHostContext, MainContext, MainThreadUrlsShape, ExtHostUrlsShape } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from '../common/extHostCustomers'; -import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; +import { IURLService, IURLHandler, IOpenURLOptions } from 'vs/platform/url/common/url'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IExtensionUrlHandler } from 'vs/workbench/services/extensions/browser/extensionUrlHandler'; @@ -19,7 +19,7 @@ class ExtensionUrlHandler implements IURLHandler { readonly extensionId: ExtensionIdentifier ) { } - handleURL(uri: URI): Promise { + handleURL(uri: URI, options?: IOpenURLOptions): Promise { if (!ExtensionIdentifier.equals(this.extensionId, uri.authority)) { return Promise.resolve(false); } diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 38d79a7843..b0cf28435e 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -5,6 +5,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; import { isWeb } from 'vs/base/common/platform'; import { startsWith } from 'vs/base/common/strings'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -14,13 +15,14 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IProductService } from 'vs/platform/product/common/productService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ExtHostContext, ExtHostWebviewsShape, IExtHostContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelShowOptions, WebviewPanelViewStateData } from 'vs/workbench/api/common/extHost.protocol'; +import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor'; import { IEditorInput } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { CustomFileEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; +import { WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; -import { ICreateWebViewShowOptions, IWebviewEditorService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewEditorService'; +import { ICreateWebViewShowOptions, IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -29,7 +31,7 @@ import { extHostNamedCustomer } from '../common/extHostCustomers'; /** * Bi-directional map between webview handles and inputs. */ -class WebviewHandleStore { +class WebviewInputStore { private readonly _handlesToInputs = new Map(); private readonly _inputsToHandles = new Map(); @@ -59,47 +61,66 @@ class WebviewHandleStore { } } -@extHostNamedCustomer(MainContext.MainThreadWebviews) -export class MainThreadWebviews extends Disposable implements MainThreadWebviewsShape { +class WebviewViewTypeTransformer { + public constructor( + public readonly prefix: string, + ) { } + + public fromExternal(viewType: string): string { + return this.prefix + viewType; + } + + public toExternal(viewType: string): string | undefined { + return startsWith(viewType, this.prefix) + ? viewType.substr(this.prefix.length) + : undefined; + } +} + +const webviewPanelViewType = new WebviewViewTypeTransformer('mainThreadWebview-'); + +@extHostNamedCustomer(extHostProtocol.MainContext.MainThreadWebviews) +export class MainThreadWebviews extends Disposable implements extHostProtocol.MainThreadWebviewsShape { private static readonly standardSupportedLinkSchemes = new Set([ - 'http', - 'https', - 'mailto', - 'vscode', + Schemas.http, + Schemas.https, + Schemas.mailto, + Schemas.vscode, 'vscode-insider', ]); - private readonly _proxy: ExtHostWebviewsShape; - private readonly _webviewEditorInputs = new WebviewHandleStore(); + private readonly _proxy: extHostProtocol.ExtHostWebviewsShape; + private readonly _webviewInputs = new WebviewInputStore(); private readonly _revivers = new Map(); private readonly _editorProviders = new Map(); constructor( - context: IExtHostContext, + context: extHostProtocol.IExtHostContext, @IExtensionService extensionService: IExtensionService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IEditorService private readonly _editorService: IEditorService, - @IWebviewEditorService private readonly _webviewEditorService: IWebviewEditorService, @IOpenerService private readonly _openerService: IOpenerService, - @ITelemetryService private readonly _telemetryService: ITelemetryService, @IProductService private readonly _productService: IProductService, + @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService, ) { super(); - this._proxy = context.getProxy(ExtHostContext.ExtHostWebviews); + this._proxy = context.getProxy(extHostProtocol.ExtHostContext.ExtHostWebviews); this._register(_editorService.onDidActiveEditorChange(this.updateWebviewViewStates, this)); this._register(_editorService.onDidVisibleEditorsChange(this.updateWebviewViewStates, this)); // This reviver's only job is to activate webview panel extensions // This should trigger the real reviver to be registered from the extension host side. - this._register(_webviewEditorService.registerResolver({ + this._register(_webviewWorkbenchService.registerResolver({ canResolve: (webview: WebviewInput) => { - if (webview.getTypeId() === CustomFileEditorInput.typeId) { + if (webview instanceof CustomFileEditorInput) { + extensionService.activateByEvent(`onWebviewEditor:${webview.viewType}`); return false; } - const viewType = this.fromInternalWebviewViewType(webview.viewType); + const viewType = webviewPanelViewType.toExternal(webview.viewType); if (typeof viewType === 'string') { extensionService.activateByEvent(`onWebviewPanel:${viewType}`); } @@ -110,13 +131,12 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews } public $createWebviewPanel( - handle: WebviewPanelHandle, + extensionData: extHostProtocol.WebviewExtensionDescription, + handle: extHostProtocol.WebviewPanelHandle, viewType: string, title: string, - showOptions: { viewColumn?: EditorViewColumn, preserveFocus?: boolean }, - options: WebviewInputOptions, - extensionId: ExtensionIdentifier, - extensionLocation: UriComponents + showOptions: { viewColumn?: EditorViewColumn, preserveFocus?: boolean; }, + options: WebviewInputOptions ): void { const mainThreadShowOptions: ICreateWebViewShowOptions = Object.create(null); if (showOptions) { @@ -124,73 +144,66 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews mainThreadShowOptions.group = viewColumnToEditorGroup(this._editorGroupService, showOptions.viewColumn); } - const webview = this._webviewEditorService.createWebview(handle, this.getInternalWebviewViewType(viewType), title, mainThreadShowOptions, reviveWebviewOptions(options), { - location: URI.revive(extensionLocation), - id: extensionId - }); + const extension = reviveWebviewExtension(extensionData); + const webview = this._webviewWorkbenchService.createWebview(handle, webviewPanelViewType.fromExternal(viewType), title, mainThreadShowOptions, reviveWebviewOptions(options), extension); this.hookupWebviewEventDelegate(handle, webview); - this._webviewEditorInputs.add(handle, webview); + this._webviewInputs.add(handle, webview); /* __GDPR__ "webviews:createWebviewPanel" : { "extensionId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ - this._telemetryService.publicLog('webviews:createWebviewPanel', { extensionId: extensionId.value }); + this._telemetryService.publicLog('webviews:createWebviewPanel', { extensionId: extension.id.value }); } - public $disposeWebview(handle: WebviewPanelHandle): void { - const webview = this.getWebviewEditorInput(handle); + public $disposeWebview(handle: extHostProtocol.WebviewPanelHandle): void { + const webview = this.getWebviewInput(handle); webview.dispose(); } - public $setTitle(handle: WebviewPanelHandle, value: string): void { - const webview = this.getWebviewEditorInput(handle); + public $setTitle(handle: extHostProtocol.WebviewPanelHandle, value: string): void { + const webview = this.getWebviewInput(handle); webview.setName(value); } - public $setState(handle: WebviewPanelHandle, state: modes.WebviewContentState): void { - const webview = this.getWebviewEditorInput(handle); + public $setState(handle: extHostProtocol.WebviewPanelHandle, state: modes.WebviewContentState): void { + const webview = this.getWebviewInput(handle); if (webview instanceof CustomFileEditorInput) { webview.setState(state); } } - public $setIconPath(handle: WebviewPanelHandle, value: { light: UriComponents, dark: UriComponents } | undefined): void { - const webview = this.getWebviewEditorInput(handle); + public $setIconPath(handle: extHostProtocol.WebviewPanelHandle, value: { light: UriComponents, dark: UriComponents; } | undefined): void { + const webview = this.getWebviewInput(handle); webview.iconPath = reviveWebviewIcon(value); } - public $setHtml(handle: WebviewPanelHandle, value: string): void { - const webview = this.getWebviewEditorInput(handle); + public $setHtml(handle: extHostProtocol.WebviewPanelHandle, value: string): void { + const webview = this.getWebviewInput(handle); webview.webview.html = value; } - public $setOptions(handle: WebviewPanelHandle, options: modes.IWebviewOptions): void { - const webview = this.getWebviewEditorInput(handle); - webview.webview.contentOptions = reviveWebviewOptions(options as any /*todo@mat */); + public $setOptions(handle: extHostProtocol.WebviewPanelHandle, options: modes.IWebviewOptions): void { + const webview = this.getWebviewInput(handle); + webview.webview.contentOptions = reviveWebviewOptions(options); } - public $setExtension(handle: WebviewPanelHandle, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): void { - const webview = this.getWebviewEditorInput(handle); - webview.webview.extension = { id: extensionId, location: URI.revive(extensionLocation) }; - } - - public $reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void { - const webview = this.getWebviewEditorInput(handle); + public $reveal(handle: extHostProtocol.WebviewPanelHandle, showOptions: extHostProtocol.WebviewPanelShowOptions): void { + const webview = this.getWebviewInput(handle); if (webview.isDisposed()) { return; } const targetGroup = this._editorGroupService.getGroup(viewColumnToEditorGroup(this._editorGroupService, showOptions.viewColumn)) || this._editorGroupService.getGroup(webview.group || 0); if (targetGroup) { - this._webviewEditorService.revealWebview(webview, targetGroup, !!showOptions.preserveFocus); + this._webviewWorkbenchService.revealWebview(webview, targetGroup, !!showOptions.preserveFocus); } } - public async $postMessage(handle: WebviewPanelHandle, message: any): Promise { - const webview = this.getWebviewEditorInput(handle); + public async $postMessage(handle: extHostProtocol.WebviewPanelHandle, message: any): Promise { + const webview = this.getWebviewInput(handle); webview.webview.sendMessage(message); return true; } @@ -200,35 +213,35 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews throw new Error(`Reviver for ${viewType} already registered`); } - this._revivers.set(viewType, this._webviewEditorService.registerResolver({ - canResolve: (webviewEditorInput) => { - return webviewEditorInput.viewType === this.getInternalWebviewViewType(viewType); + this._revivers.set(viewType, this._webviewWorkbenchService.registerResolver({ + canResolve: (webviewInput) => { + return webviewInput.viewType === webviewPanelViewType.fromExternal(viewType); }, - resolveWebview: async (webviewEditorInput): Promise => { - const viewType = this.fromInternalWebviewViewType(webviewEditorInput.viewType); + resolveWebview: async (webviewInput): Promise => { + const viewType = webviewPanelViewType.toExternal(webviewInput.viewType); if (!viewType) { - webviewEditorInput.webview.html = MainThreadWebviews.getDeserializationFailedContents(webviewEditorInput.viewType); + webviewInput.webview.html = MainThreadWebviews.getDeserializationFailedContents(webviewInput.viewType); return; } - const handle = webviewEditorInput.id; - this._webviewEditorInputs.add(handle, webviewEditorInput); - this.hookupWebviewEventDelegate(handle, webviewEditorInput); + const handle = webviewInput.id; + this._webviewInputs.add(handle, webviewInput); + this.hookupWebviewEventDelegate(handle, webviewInput); let state = undefined; - if (webviewEditorInput.webview.state) { + if (webviewInput.webview.state) { try { - state = JSON.parse(webviewEditorInput.webview.state); + state = JSON.parse(webviewInput.webview.state); } catch { // noop } } try { - await this._proxy.$deserializeWebviewPanel(handle, viewType, webviewEditorInput.getTitle(), state, editorGroupToViewColumn(this._editorGroupService, webviewEditorInput.group || 0), webviewEditorInput.webview.options); + await this._proxy.$deserializeWebviewPanel(handle, viewType, webviewInput.getTitle(), state, editorGroupToViewColumn(this._editorGroupService, webviewInput.group || 0), webviewInput.webview.options); } catch (error) { onUnexpectedError(error); - webviewEditorInput.webview.html = MainThreadWebviews.getDeserializationFailedContents(viewType); + webviewInput.webview.html = MainThreadWebviews.getDeserializationFailedContents(viewType); } } })); @@ -244,39 +257,43 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews this._revivers.delete(viewType); } - public $registerEditorProvider(viewType: string): void { + public $registerEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void { if (this._editorProviders.has(viewType)) { throw new Error(`Provider for ${viewType} already registered`); } - this._editorProviders.set(viewType, this._webviewEditorService.registerResolver({ - canResolve: (webviewEditorInput) => { - return webviewEditorInput.getTypeId() !== WebviewInput.typeId && webviewEditorInput.viewType === viewType; - }, - resolveWebview: async (webview) => { - const handle = webview.id; - this._webviewEditorInputs.add(handle, webview); - this.hookupWebviewEventDelegate(handle, webview); + const extension = reviveWebviewExtension(extensionData); - if (webview instanceof CustomFileEditorInput) { - webview.onWillSave(e => { + this._editorProviders.set(viewType, this._webviewWorkbenchService.registerResolver({ + canResolve: (webviewInput) => { + return webviewInput instanceof CustomFileEditorInput && webviewInput.viewType === viewType; + }, + resolveWebview: async (webviewInput) => { + const handle = webviewInput.id; + this._webviewInputs.add(handle, webviewInput); + this.hookupWebviewEventDelegate(handle, webviewInput); + + webviewInput.webview.options = options; + webviewInput.webview.extension = extension; + + if (webviewInput instanceof CustomFileEditorInput) { + webviewInput.onWillSave(e => { e.waitUntil(this._proxy.$save(handle)); }); } try { await this._proxy.$resolveWebviewEditor( - webview.getResource(), + webviewInput.getResource(), handle, viewType, - webview.getTitle(), - webview.webview.state, - editorGroupToViewColumn(this._editorGroupService, webview.group || 0), - webview.webview.options + webviewInput.getTitle(), + editorGroupToViewColumn(this._editorGroupService, webviewInput.group || 0), + webviewInput.webview.options ); } catch (error) { onUnexpectedError(error); - webview.webview.html = MainThreadWebviews.getDeserializationFailedContents(viewType); + webviewInput.webview.html = MainThreadWebviews.getDeserializationFailedContents(viewType); } } })); @@ -292,42 +309,24 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews this._editorProviders.delete(viewType); } - private getInternalWebviewViewType(viewType: string): string { - return `mainThreadWebview-${viewType}`; - } - - private fromInternalWebviewViewType(viewType: string): string | undefined { - if (!startsWith(viewType, 'mainThreadWebview-')) { - return undefined; - } - return viewType.replace(/^mainThreadWebview-/, ''); - } - - private hookupWebviewEventDelegate(handle: WebviewPanelHandle, input: WebviewInput) { + private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewPanelHandle, input: WebviewInput) { input.webview.onDidClickLink((uri: URI) => this.onDidClickLink(handle, uri)); input.webview.onMessage((message: any) => this._proxy.$onMessage(handle, message)); input.onDispose(() => { this._proxy.$onDidDisposeWebviewPanel(handle).finally(() => { - this._webviewEditorInputs.delete(handle); + this._webviewInputs.delete(handle); }); }); - input.webview.onDidUpdateState((newState: any) => { - const webview = this.tryGetWebviewEditorInput(handle); - if (!webview || webview.isDisposed()) { - return; - } - webview.webview.state = newState; - }); input.webview.onMissingCsp((extension: ExtensionIdentifier) => this._proxy.$onMissingCsp(handle, extension.value)); } private updateWebviewViewStates() { - if (!this._webviewEditorInputs.size) { + if (!this._webviewInputs.size) { return; } const activeInput = this._editorService.activeControl && this._editorService.activeControl.input; - const viewStates: WebviewPanelViewStateData = {}; + const viewStates: extHostProtocol.WebviewPanelViewStateData = {}; const updateViewStatesForInput = (group: IEditorGroup, topLevelInput: IEditorInput, editorInput: IEditorInput) => { if (!(editorInput instanceof WebviewInput)) { @@ -336,11 +335,11 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews editorInput.updateGroup(group.id); - const handle = this._webviewEditorInputs.getHandleForInput(editorInput); + const handle = this._webviewInputs.getHandleForInput(editorInput); if (handle) { viewStates[handle] = { - visible: topLevelInput.matches(group.activeEditor), - active: topLevelInput.matches(activeInput), + visible: topLevelInput === group.activeEditor, + active: topLevelInput === activeInput, position: editorGroupToViewColumn(this._editorGroupService, group.id), }; } @@ -362,10 +361,10 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews } } - private onDidClickLink(handle: WebviewPanelHandle, link: URI): void { - const webview = this.getWebviewEditorInput(handle); + private onDidClickLink(handle: extHostProtocol.WebviewPanelHandle, link: URI): void { + const webview = this.getWebviewInput(handle); if (this.isSupportedLink(webview, link)) { - this._openerService.open(link); + this._openerService.open(link, { fromUserGesture: true }); } } @@ -376,19 +375,19 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews if (!isWeb && this._productService.urlProtocol === link.scheme) { return true; } - return !!webview.webview.contentOptions.enableCommandUris && link.scheme === 'command'; + return !!webview.webview.contentOptions.enableCommandUris && link.scheme === Schemas.command; } - private getWebviewEditorInput(handle: WebviewPanelHandle): WebviewInput { - const webview = this.tryGetWebviewEditorInput(handle); + private getWebviewInput(handle: extHostProtocol.WebviewPanelHandle): WebviewInput { + const webview = this.tryGetWebviewInput(handle); if (!webview) { throw new Error('Unknown webview handle:' + handle); } return webview; } - private tryGetWebviewEditorInput(handle: WebviewPanelHandle): WebviewInput | undefined { - return this._webviewEditorInputs.getInputForHandle(handle); + private tryGetWebviewInput(handle: extHostProtocol.WebviewPanelHandle): WebviewInput | undefined { + return this._webviewInputs.getInputForHandle(handle); } private static getDeserializationFailedContents(viewType: string) { @@ -403,6 +402,10 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews } } +function reviveWebviewExtension(extensionData: extHostProtocol.WebviewExtensionDescription): WebviewExtensionDescription { + return { id: extensionData.id, location: URI.revive(extensionData.location) }; +} + function reviveWebviewOptions(options: modes.IWebviewOptions): WebviewInputOptions { return { ...options, @@ -411,10 +414,9 @@ function reviveWebviewOptions(options: modes.IWebviewOptions): WebviewInputOptio }; } - function reviveWebviewIcon( - value: { light: UriComponents, dark: UriComponents } | undefined -): { light: URI, dark: URI } | undefined { + value: { light: UriComponents, dark: UriComponents; } | undefined +): { light: URI, dark: URI; } | undefined { if (!value) { return undefined; } diff --git a/src/vs/workbench/api/browser/media/test.svg b/src/vs/workbench/api/browser/media/test.svg index 57cd408942..569947a033 100644 --- a/src/vs/workbench/api/browser/media/test.svg +++ b/src/vs/workbench/api/browser/media/test.svg @@ -1,10 +1,3 @@ - - - - - - - - + + - diff --git a/src/vs/workbench/api/common/apiCommands.ts b/src/vs/workbench/api/common/apiCommands.ts index c363fb9083..c8c79c7416 100644 --- a/src/vs/workbench/api/common/apiCommands.ts +++ b/src/vs/workbench/api/common/apiCommands.ts @@ -39,7 +39,7 @@ interface IOpenFolderAPICommandOptions { } export class OpenFolderAPICommand { - public static ID = 'vscode.openFolder'; + public static readonly ID = 'vscode.openFolder'; public static execute(executor: ICommandsExecutor, uri?: URI, forceNewWindow?: boolean): Promise; public static execute(executor: ICommandsExecutor, uri?: URI, options?: IOpenFolderAPICommandOptions): Promise; public static execute(executor: ICommandsExecutor, uri?: URI, arg: boolean | IOpenFolderAPICommandOptions = {}): Promise { @@ -73,7 +73,7 @@ interface INewWindowAPICommandOptions { } export class NewWindowAPICommand { - public static ID = 'vscode.newWindow'; + public static readonly ID = 'vscode.newWindow'; public static execute(executor: ICommandsExecutor, options?: INewWindowAPICommandOptions): Promise { const commandOptions: IOpenEmptyWindowOptions = { forceReuseWindow: options && options.reuseWindow, @@ -94,7 +94,7 @@ CommandsRegistry.registerCommand({ }); export class DiffAPICommand { - public static ID = 'vscode.diff'; + public static readonly ID = 'vscode.diff'; public static execute(executor: ICommandsExecutor, left: URI, right: URI, label: string, options?: vscode.TextDocumentShowOptions): Promise { return executor.executeCommand('_workbench.diff', [ left, right, @@ -108,7 +108,7 @@ export class DiffAPICommand { CommandsRegistry.registerCommand(DiffAPICommand.ID, adjustHandler(DiffAPICommand.execute)); export class OpenAPICommand { - public static ID = 'vscode.open'; + public static readonly ID = 'vscode.open'; public static execute(executor: ICommandsExecutor, resource: URI, columnOrOptions?: vscode.ViewColumn | vscode.TextDocumentShowOptions, label?: string): Promise { let options: ITextEditorOptions | undefined; let position: EditorViewColumn | undefined; @@ -138,7 +138,7 @@ CommandsRegistry.registerCommand('_workbench.removeFromRecentlyOpened', function }); export class RemoveFromRecentlyOpenedAPICommand { - public static ID = 'vscode.removeFromRecentlyOpened'; + public static readonly ID = 'vscode.removeFromRecentlyOpened'; public static execute(executor: ICommandsExecutor, path: string | URI): Promise { if (typeof path === 'string') { path = path.match(/^[^:/?#]+:\/\//) ? URI.parse(path) : URI.file(path); @@ -151,7 +151,7 @@ export class RemoveFromRecentlyOpenedAPICommand { CommandsRegistry.registerCommand(RemoveFromRecentlyOpenedAPICommand.ID, adjustHandler(RemoveFromRecentlyOpenedAPICommand.execute)); export class OpenIssueReporter { - public static ID = 'vscode.openIssueReporter'; + public static readonly ID = 'vscode.openIssueReporter'; public static execute(executor: ICommandsExecutor, extensionId: string): Promise { return executor.executeCommand('workbench.action.openIssueReporter', [extensionId]); } @@ -185,7 +185,7 @@ CommandsRegistry.registerCommand('_workbench.getRecentlyOpened', async function }); export class SetEditorLayoutAPICommand { - public static ID = 'vscode.setEditorLayout'; + public static readonly ID = 'vscode.setEditorLayout'; public static execute(executor: ICommandsExecutor, layout: EditorGroupLayout): Promise { return executor.executeCommand('layoutEditorGroups', layout); } diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index 31d712567a..e67016a30f 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -10,7 +10,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { IConfigurationNode, IConfigurationRegistry, Extensions, editorConfigurationSchemaId, IDefaultConfigurationExtension, validateProperty, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; -import { workspaceSettingsSchemaId, launchSchemaId } from 'vs/workbench/services/configuration/common/configuration'; +import { workspaceSettingsSchemaId, launchSchemaId, tasksSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { isObject } from 'vs/base/common/types'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -296,6 +296,12 @@ jsonRegistry.registerSchema('vscode://schemas/workspaceConfig', { description: nls.localize('workspaceConfig.launch.description', "Workspace launch configurations"), $ref: launchSchemaId }, + 'tasks': { + type: 'object', + default: { version: '2.0.0', tasks: [] }, + description: nls.localize('workspaceConfig.tasks.description', "Workspace task configurations"), + $ref: tasksSchemaId + }, 'extensions': { type: 'object', default: {}, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index d15c877dbf..341be68203 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -105,7 +105,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostOutputService = rpcProtocol.set(ExtHostContext.ExtHostOutputService, accessor.get(IExtHostOutputService)); // manually create and register addressable instances - const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.environment)); + const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.environment, extHostWorkspace)); const extHostUrls = rpcProtocol.set(ExtHostContext.ExtHostUrls, new ExtHostUrls(rpcProtocol)); const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors)); const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService)); @@ -250,14 +250,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostWindow.openUri(uri, { allowTunneling: !!initData.remote.isRemote }); }, asExternalUri(uri: URI) { - checkProposedApiEnabled(extension); return extHostWindow.asExternalUri(uri, { allowTunneling: !!initData.remote.isRemote }); }, get remoteName() { return getRemoteName(initData.remote.authority); }, get uiKind() { - checkProposedApiEnabled(extension); return initData.uiKind; } }; @@ -533,11 +531,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostTreeViews.createTreeView(viewId, options, extension); }, registerWebviewPanelSerializer: (viewType: string, serializer: vscode.WebviewPanelSerializer) => { - return extHostWebviews.registerWebviewPanelSerializer(viewType, serializer); + return extHostWebviews.registerWebviewPanelSerializer(extension, viewType, serializer); }, - registerWebviewEditorProvider: (viewType: string, provider: vscode.WebviewEditorProvider) => { + registerWebviewEditorProvider: (viewType: string, provider: vscode.WebviewEditorProvider, options?: vscode.WebviewPanelOptions) => { checkProposedApiEnabled(extension); - return extHostWebviews.registerWebviewEditorProvider(extension, viewType, provider); + return extHostWebviews.registerWebviewEditorProvider(extension, viewType, provider, options); }, registerDecorationProvider(provider: vscode.DecorationProvider) { checkProposedApiEnabled(extension); @@ -635,7 +633,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const options = uriOrFileNameOrOptions as { language?: string; content?: string; }; if (typeof uriOrFileNameOrOptions === 'string') { uriPromise = Promise.resolve(URI.file(uriOrFileNameOrOptions)); - } else if (uriOrFileNameOrOptions instanceof URI) { + } else if (URI.isUri(uriOrFileNameOrOptions)) { uriPromise = Promise.resolve(uriOrFileNameOrOptions); } else if (!options || typeof options === 'object') { uriPromise = extHostDocuments.createDocumentData(options); @@ -850,7 +848,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I EndOfLine: extHostTypes.EndOfLine, EventEmitter: Emitter, ExtensionKind: extHostTypes.ExtensionKind, - CustomExecution2: extHostTypes.CustomExecution2, + CustomExecution: extHostTypes.CustomExecution, FileChangeType: extHostTypes.FileChangeType, FileSystemError: extHostTypes.FileSystemError, FileType: files.FileType, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 53d1932dbb..4f0b42adc7 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -509,7 +509,7 @@ export interface MainThreadQuickOpenShape extends IDisposable { } export interface MainThreadStatusBarShape extends IDisposable { - $setEntry(id: number, statusId: string, statusName: string, 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 | undefined, command: string | undefined, color: string | ThemeColor | undefined, alignment: statusbar.StatusbarAlignment, priority: number | undefined): void; $dispose(id: number): void; } @@ -544,8 +544,13 @@ export interface WebviewPanelShowOptions { readonly preserveFocus?: boolean; } +export interface WebviewExtensionDescription { + readonly id: ExtensionIdentifier; + readonly location: UriComponents; +} + export interface MainThreadWebviewsShape extends IDisposable { - $createWebviewPanel(handle: WebviewPanelHandle, viewType: string, title: string, showOptions: WebviewPanelShowOptions, options: modes.IWebviewPanelOptions & modes.IWebviewOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): void; + $createWebviewPanel(extension: WebviewExtensionDescription, handle: WebviewPanelHandle, viewType: string, title: string, showOptions: WebviewPanelShowOptions, options: modes.IWebviewPanelOptions & modes.IWebviewOptions): void; $disposeWebview(handle: WebviewPanelHandle): void; $reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void; $setTitle(handle: WebviewPanelHandle, value: string): void; @@ -554,14 +559,13 @@ export interface MainThreadWebviewsShape extends IDisposable { $setHtml(handle: WebviewPanelHandle, value: string): void; $setOptions(handle: WebviewPanelHandle, options: modes.IWebviewOptions): void; - $setExtension(handle: WebviewPanelHandle, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): void; $postMessage(handle: WebviewPanelHandle, value: any): Promise; $registerSerializer(viewType: string): void; $unregisterSerializer(viewType: string): void; - $registerEditorProvider(viewType: string): void; + $registerEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void; $unregisterEditorProvider(viewType: string): void; } @@ -579,7 +583,7 @@ export interface ExtHostWebviewsShape { $onDidChangeWebviewPanelViewStates(newState: WebviewPanelViewStateData): void; $onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise; $deserializeWebviewPanel(newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; - $resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; + $resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; $save(handle: WebviewPanelHandle): Promise; } diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 02cb4f00b2..91b750ebee 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import * as vscode from 'vscode'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as types from 'vs/workbench/api/common/extHostTypes'; @@ -27,7 +27,7 @@ export class ExtHostApiCommands { } private _commands: ExtHostCommands; - private _disposables: IDisposable[] = []; + private readonly _disposables = new DisposableStore(); private constructor(commands: ExtHostCommands) { this._commands = commands; @@ -239,7 +239,7 @@ export class ExtHostApiCommands { this._register(OpenFolderAPICommand.ID, adjustHandler(OpenFolderAPICommand.execute), { description: 'Open a folder or workspace in the current window or new window depending on the newWindow argument. Note that opening in the same window will shutdown the current extension host process and start a new one on the given folder/workspace unless the newWindow parameter is set to true.', args: [ - { name: 'uri', description: '(optional) Uri of the folder or workspace file to open. If not provided, a native dialog will ask the user for the folder', constraint: (value: any) => value === undefined || value instanceof URI }, + { name: 'uri', description: '(optional) Uri of the folder or workspace file to open. If not provided, a native dialog will ask the user for the folder', constraint: (value: any) => value === undefined || URI.isUri(value) }, { name: 'options', description: '(optional) Options. Object with the following properties: `forceNewWindow `: Whether to open the folder/workspace in a new window or the same. Defaults to opening in the same window. `noRecentEntry`: Whether the opened URI will appear in the \'Open Recent\' list. Defaults to true. Note, for backward compatibility, options can also be of type boolean, representing the `forceNewWindow` setting.', constraint: (value: any) => value === undefined || typeof value === 'object' || typeof value === 'boolean' } ] }); @@ -288,7 +288,7 @@ export class ExtHostApiCommands { private _register(id: string, handler: (...args: any[]) => any, description?: ICommandHandlerDescription): void { const disposable = this._commands.registerCommand(false, id, handler, this, description); - this._disposables.push(disposable); + this._disposables.add(disposable); } /** @@ -576,30 +576,30 @@ export class ExtHostApiCommands { private async _executeCallHierarchyIncomingCallsProvider(resource: URI, position: types.Position): Promise { type IncomingCallDto = { - source: ICallHierarchyItemDto; - sourceRanges: IRange[]; + from: ICallHierarchyItemDto; + fromRanges: IRange[]; }; const args = { resource, position: typeConverters.Position.from(position) }; const calls = await this._commands.executeCommand('_executeCallHierarchyIncomingCalls', args); const result: vscode.CallHierarchyIncomingCall[] = []; for (const call of calls) { - result.push(new types.CallHierarchyIncomingCall(typeConverters.CallHierarchyItem.to(call.source), call.sourceRanges.map(typeConverters.Range.to))); + result.push(new types.CallHierarchyIncomingCall(typeConverters.CallHierarchyItem.to(call.from), call.fromRanges.map(typeConverters.Range.to))); } return result; } private async _executeCallHierarchyOutgoingCallsProvider(resource: URI, position: types.Position): Promise { type OutgoingCallDto = { - sourceRanges: IRange[]; - target: ICallHierarchyItemDto; + fromRanges: IRange[]; + to: ICallHierarchyItemDto; }; const args = { resource, position: typeConverters.Position.from(position) }; const calls = await this._commands.executeCommand('_executeCallHierarchyOutgoingCalls', args); const result: vscode.CallHierarchyOutgoingCall[] = []; for (const call of calls) { - result.push(new types.CallHierarchyOutgoingCall(typeConverters.CallHierarchyItem.to(call.target), call.sourceRanges.map(typeConverters.Range.to))); + result.push(new types.CallHierarchyOutgoingCall(typeConverters.CallHierarchyItem.to(call.to), call.fromRanges.map(typeConverters.Range.to))); } return result; } diff --git a/src/vs/workbench/api/common/extHostDiagnostics.ts b/src/vs/workbench/api/common/extHostDiagnostics.ts index 31d4c33104..7c810fb08f 100644 --- a/src/vs/workbench/api/common/extHostDiagnostics.ts +++ b/src/vs/workbench/api/common/extHostDiagnostics.ts @@ -64,7 +64,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { this._checkDisposed(); let toSync: vscode.Uri[] = []; - if (first instanceof URI) { + if (URI.isUri(first)) { if (!diagnostics) { // remove this entry diff --git a/src/vs/workbench/api/common/extHostDocumentData.ts b/src/vs/workbench/api/common/extHostDocumentData.ts index b4a02faa2c..ebffa07ccf 100644 --- a/src/vs/workbench/api/common/extHostDocumentData.ts +++ b/src/vs/workbench/api/common/extHostDocumentData.ts @@ -12,6 +12,7 @@ import { ensureValidWordDefinition, getWordAtText } from 'vs/editor/common/model import { MainThreadDocumentsShape } from 'vs/workbench/api/common/extHost.protocol'; import { EndOfLine, Position, Range } from 'vs/workbench/api/common/extHostTypes'; import * as vscode from 'vscode'; +import { equals } from 'vs/base/common/arrays'; const _modeId2WordDefinition = new Map(); export function setWordDefinitionFor(modeId: string, wordDefinition: RegExp | undefined): void { @@ -48,17 +49,8 @@ export class ExtHostDocumentData extends MirrorTextModel { this._isDirty = false; } - equalLines(lines: string[]): boolean { - const len = lines.length; - if (len !== this._lines.length) { - return false; - } - for (let i = 0; i < len; i++) { - if (lines[i] !== this._lines[i]) { - return false; - } - } - return true; + equalLines(lines: readonly string[]): boolean { + return equals(this._lines, lines); } get document(): vscode.TextDocument { diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 217f5cf76e..9aa296b0e2 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -1021,7 +1021,7 @@ class CallHierarchyAdapter { if (!calls) { return undefined; } - return calls.map(call => (<[extHostProtocol.ICallHierarchyItemDto, IRange[]]>[typeConvert.CallHierarchyItem.from(call.source), call.sourceRanges.map(typeConvert.Range.from)])); + return calls.map(call => (<[extHostProtocol.ICallHierarchyItemDto, IRange[]]>[typeConvert.CallHierarchyItem.from(call.from), call.fromRanges.map(typeConvert.Range.from)])); } async provideCallsFrom(uri: URI, position: IPosition, token: CancellationToken): Promise<[extHostProtocol.ICallHierarchyItemDto, IRange[]][] | undefined> { @@ -1031,7 +1031,7 @@ class CallHierarchyAdapter { if (!calls) { return undefined; } - return calls.map(call => (<[extHostProtocol.ICallHierarchyItemDto, IRange[]]>[typeConvert.CallHierarchyItem.from(call.target), call.sourceRanges.map(typeConvert.Range.from)])); + return calls.map(call => (<[extHostProtocol.ICallHierarchyItemDto, IRange[]]>[typeConvert.CallHierarchyItem.from(call.to), call.fromRanges.map(typeConvert.Range.from)])); } } diff --git a/src/vs/workbench/api/common/extHostQuickOpen.ts b/src/vs/workbench/api/common/extHostQuickOpen.ts index d05d24ad64..68c953e298 100644 --- a/src/vs/workbench/api/common/extHostQuickOpen.ts +++ b/src/vs/workbench/api/common/extHostQuickOpen.ts @@ -234,16 +234,16 @@ class ExtHostQuickInput implements QuickInput { private static _nextId = 1; _id = ExtHostQuickPick._nextId++; - private _title: string; - private _steps: number; - private _totalSteps: number; + private _title: string | undefined; + private _steps: number | undefined; + private _totalSteps: number | undefined; private _visible = false; private _expectingHide = false; private _enabled = true; private _busy = false; private _ignoreFocusOut = true; private _value = ''; - private _placeholder: string; + private _placeholder: string | undefined; private _buttons: QuickInputButton[] = []; private _handlesToButtons = new Map(); private readonly _onDidAcceptEmitter = new Emitter(); @@ -268,7 +268,7 @@ class ExtHostQuickInput implements QuickInput { return this._title; } - set title(title: string) { + set title(title: string | undefined) { this._title = title; this.update({ title }); } @@ -277,7 +277,7 @@ class ExtHostQuickInput implements QuickInput { return this._steps; } - set step(step: number) { + set step(step: number | undefined) { this._steps = step; this.update({ step }); } @@ -286,7 +286,7 @@ class ExtHostQuickInput implements QuickInput { return this._totalSteps; } - set totalSteps(totalSteps: number) { + set totalSteps(totalSteps: number | undefined) { this._totalSteps = totalSteps; this.update({ totalSteps }); } @@ -331,7 +331,7 @@ class ExtHostQuickInput implements QuickInput { return this._placeholder; } - set placeholder(placeholder: string) { + set placeholder(placeholder: string | undefined) { this._placeholder = placeholder; this.update({ placeholder }); } @@ -398,7 +398,7 @@ class ExtHostQuickInput implements QuickInput { } } - public dispose(): void { + dispose(): void { if (this._disposed) { return; } @@ -455,7 +455,7 @@ function getIconUris(iconPath: QuickInputButton['iconPath']): { dark: URI, light function getLightIconUri(iconPath: QuickInputButton['iconPath']) { if (iconPath && !(iconPath instanceof ThemeIcon)) { if (typeof iconPath === 'string' - || iconPath instanceof URI) { + || URI.isUri(iconPath)) { return getIconUri(iconPath); } return getIconUri((iconPath as any).light); @@ -471,7 +471,7 @@ function getDarkIconUri(iconPath: QuickInputButton['iconPath']) { } function getIconUri(iconPath: string | URI) { - if (iconPath instanceof URI) { + if (URI.isUri(iconPath)) { return iconPath; } return URI.file(iconPath); @@ -587,9 +587,9 @@ class ExtHostQuickPick extends ExtHostQuickInput implem class ExtHostInputBox extends ExtHostQuickInput implements InputBox { - private _password: boolean; - private _prompt: string; - private _validationMessage: string; + private _password = false; + private _prompt: string | undefined; + private _validationMessage: string | undefined; constructor(proxy: MainThreadQuickOpenShape, extensionId: ExtensionIdentifier, onDispose: () => void) { super(proxy, extensionId, onDispose); @@ -609,7 +609,7 @@ class ExtHostInputBox extends ExtHostQuickInput implements InputBox { return this._prompt; } - set prompt(prompt: string) { + set prompt(prompt: string | undefined) { this._prompt = prompt; this.update({ prompt }); } @@ -618,7 +618,7 @@ class ExtHostInputBox extends ExtHostQuickInput implements InputBox { return this._validationMessage; } - set validationMessage(validationMessage: string) { + set validationMessage(validationMessage: string | undefined) { this._validationMessage = validationMessage; this.update({ validationMessage }); } diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 9bac08e22c..a2f1fe8e27 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -10,7 +10,7 @@ import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { asPromise } from 'vs/base/common/async'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto } from './extHost.protocol'; -import { sortedDiff } from 'vs/base/common/arrays'; +import { sortedDiff, equals } from 'vs/base/common/arrays'; import { comparePaths } from 'vs/base/common/comparers'; import * as vscode from 'vscode'; import { ISplice } from 'vs/base/common/sequence'; @@ -126,18 +126,8 @@ function commandEquals(a: vscode.Command, b: vscode.Command): boolean { && (a.arguments && b.arguments ? compareArgs(a.arguments, b.arguments) : a.arguments === b.arguments); } -function commandListEquals(a: vscode.Command[], b: vscode.Command[]): boolean { - if (a.length !== b.length) { - return false; - } - - for (let i = 0; i < a.length; i++) { - if (!commandEquals(a[i], b[i])) { - return false; - } - } - - return true; +function commandListEquals(a: readonly vscode.Command[], b: readonly vscode.Command[]): boolean { + return equals(a, b, commandEquals); } export interface IValidateInput { @@ -174,9 +164,9 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { this._placeholder = placeholder; } - private _validateInput: IValidateInput; + private _validateInput: IValidateInput | undefined; - get validateInput(): IValidateInput { + get validateInput(): IValidateInput | undefined { if (!this._extension.enableProposedApi) { throw new Error(`[${this._extension.identifier.value}]: Proposed API is only available when running out of dev or with the following command line switch: --enable-proposed-api ${this._extension.identifier.value}`); } @@ -184,7 +174,7 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { return this._validateInput; } - set validateInput(fn: IValidateInput) { + set validateInput(fn: IValidateInput | undefined) { if (!this._extension.enableProposedApi) { throw new Error(`[${this._extension.identifier.value}]: Proposed API is only available when running out of dev or with the following command line switch: --enable-proposed-api ${this._extension.identifier.value}`); } @@ -405,6 +395,10 @@ class ExtHostSourceControl implements vscode.SourceControl { } set commitTemplate(commitTemplate: string | undefined) { + if (commitTemplate === this._commitTemplate) { + return; + } + this._commitTemplate = commitTemplate; this._proxy.$updateSourceControl(this.handle, { commitTemplate }); } @@ -667,7 +661,7 @@ export class ExtHostSCM implements ExtHostSCMShape { return Promise.resolve(undefined); } - return asPromise(() => sourceControl.inputBox.validateInput(value, cursorPosition)).then(result => { + return asPromise(() => sourceControl.inputBox.validateInput!(value, cursorPosition)).then(result => { if (!result) { return Promise.resolve(undefined); } diff --git a/src/vs/workbench/api/common/extHostStatusBar.ts b/src/vs/workbench/api/common/extHostStatusBar.ts index cebe59c750..5bb6f2f8e5 100644 --- a/src/vs/workbench/api/common/extHostStatusBar.ts +++ b/src/vs/workbench/api/common/extHostStatusBar.ts @@ -15,16 +15,16 @@ export class ExtHostStatusBarEntry implements StatusBarItem { private _id: number; private _alignment: number; private _priority?: number; - private _disposed: boolean; - private _visible: boolean; + private _disposed: boolean = false; + private _visible: boolean = false; private _statusId: string; private _statusName: string; - private _text: string; - private _tooltip: string; - private _color: string | ThemeColor; - private _command: string; + private _text: string = ''; + private _tooltip?: string; + private _color?: string | ThemeColor; + private _command?: string; private _timeoutHandle: any; private _proxy: MainThreadStatusBarShape; @@ -54,15 +54,15 @@ export class ExtHostStatusBarEntry implements StatusBarItem { return this._text; } - public get tooltip(): string { + public get tooltip(): string | undefined { return this._tooltip; } - public get color(): string | ThemeColor { + public get color(): string | ThemeColor | undefined { return this._color; } - public get command(): string { + public get command(): string | undefined { return this._command; } @@ -71,17 +71,17 @@ export class ExtHostStatusBarEntry implements StatusBarItem { this.update(); } - public set tooltip(tooltip: string) { + public set tooltip(tooltip: string | undefined) { this._tooltip = tooltip; this.update(); } - public set color(color: string | ThemeColor) { + public set color(color: string | ThemeColor | undefined) { this._color = color; this.update(); } - public set command(command: string) { + public set command(command: string | undefined) { this._command = command; this.update(); } diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts index 3f098b92d8..47952ed61e 100644 --- a/src/vs/workbench/api/common/extHostTask.ts +++ b/src/vs/workbench/api/common/extHostTask.ts @@ -88,7 +88,7 @@ export namespace ProcessExecutionOptionsDTO { } export namespace ProcessExecutionDTO { - export function is(value: tasks.ShellExecutionDTO | tasks.ProcessExecutionDTO | tasks.CustomExecution2DTO | undefined): value is tasks.ProcessExecutionDTO { + export function is(value: tasks.ShellExecutionDTO | tasks.ProcessExecutionDTO | tasks.CustomExecutionDTO | undefined): value is tasks.ProcessExecutionDTO { if (value) { const candidate = value as tasks.ProcessExecutionDTO; return candidate && !!candidate.process; @@ -133,7 +133,7 @@ export namespace ShellExecutionOptionsDTO { } export namespace ShellExecutionDTO { - export function is(value: tasks.ShellExecutionDTO | tasks.ProcessExecutionDTO | tasks.CustomExecution2DTO | undefined): value is tasks.ShellExecutionDTO { + export function is(value: tasks.ShellExecutionDTO | tasks.ProcessExecutionDTO | tasks.CustomExecutionDTO | undefined): value is tasks.ShellExecutionDTO { if (value) { const candidate = value as tasks.ShellExecutionDTO; return candidate && (!!candidate.commandLine || !!candidate.command); @@ -170,19 +170,19 @@ export namespace ShellExecutionDTO { } } -export namespace CustomExecution2DTO { - export function is(value: tasks.ShellExecutionDTO | tasks.ProcessExecutionDTO | tasks.CustomExecution2DTO | undefined): value is tasks.CustomExecution2DTO { +export namespace CustomExecutionDTO { + export function is(value: tasks.ShellExecutionDTO | tasks.ProcessExecutionDTO | tasks.CustomExecutionDTO | undefined): value is tasks.CustomExecutionDTO { if (value) { - let candidate = value as tasks.CustomExecution2DTO; - return candidate && candidate.customExecution === 'customExecution2'; + let candidate = value as tasks.CustomExecutionDTO; + return candidate && candidate.customExecution === 'customExecution'; } else { return false; } } - export function from(value: vscode.CustomExecution2): tasks.CustomExecution2DTO { + export function from(value: vscode.CustomExecution): tasks.CustomExecutionDTO { return { - customExecution: 'customExecution2' + customExecution: 'customExecution' }; } } @@ -220,13 +220,13 @@ export namespace TaskDTO { if (value === undefined || value === null) { return undefined; } - let execution: tasks.ShellExecutionDTO | tasks.ProcessExecutionDTO | tasks.CustomExecution2DTO | undefined; + let execution: tasks.ShellExecutionDTO | tasks.ProcessExecutionDTO | tasks.CustomExecutionDTO | undefined; if (value.execution instanceof types.ProcessExecution) { execution = ProcessExecutionDTO.from(value.execution); } else if (value.execution instanceof types.ShellExecution) { execution = ShellExecutionDTO.from(value.execution); - } else if ((value).execution2 && (value).execution2 instanceof types.CustomExecution2) { - execution = CustomExecution2DTO.from((value).execution2); + } else if ((value).execution2 && (value).execution2 instanceof types.CustomExecution) { + execution = CustomExecutionDTO.from((value).execution2); } const definition: tasks.TaskDefinitionDTO | undefined = TaskDefinitionDTO.from(value.definition); @@ -373,9 +373,9 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { protected _handleCounter: number; protected _handlers: Map; protected _taskExecutions: Map; - protected _providedCustomExecutions2: Map; + protected _providedCustomExecutions2: Map; private _notProvidedCustomExecutions: Set; // Used for custom executions tasks that are created and run through executeTask. - protected _activeCustomExecutions2: Map; + protected _activeCustomExecutions2: Map; private _lastStartedTask: string | undefined; protected readonly _onDidExecuteTask: Emitter = new Emitter(); protected readonly _onDidTerminateTask: Emitter = new Emitter(); @@ -399,9 +399,9 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { this._handleCounter = 0; this._handlers = new Map(); this._taskExecutions = new Map(); - this._providedCustomExecutions2 = new Map(); + this._providedCustomExecutions2 = new Map(); this._notProvidedCustomExecutions = new Set(); - this._activeCustomExecutions2 = new Map(); + this._activeCustomExecutions2 = new Map(); } public registerTaskProvider(extension: IExtensionDescription, type: string, provider: vscode.TaskProvider): vscode.Disposable { @@ -454,15 +454,15 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { } public async $onDidStartTask(execution: tasks.TaskExecutionDTO, terminalId: number): Promise { - const execution2: types.CustomExecution2 | undefined = this._providedCustomExecutions2.get(execution.id); - if (execution2) { + const customExecution: types.CustomExecution | undefined = this._providedCustomExecutions2.get(execution.id); + if (customExecution) { if (this._activeCustomExecutions2.get(execution.id) !== undefined) { throw new Error('We should not be trying to start the same custom task executions twice.'); } // Clone the custom execution to keep the original untouched. This is important for multiple runs of the same task. - this._activeCustomExecutions2.set(execution.id, execution2); - this._terminalService.attachPtyToTerminal(terminalId, await execution2.callback()); + this._activeCustomExecutions2.set(execution.id, customExecution); + this._terminalService.attachPtyToTerminal(terminalId, await customExecution.callback()); } this._lastStartedTask = execution.id; @@ -573,8 +573,8 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { throw new Error('Unexpected: The resolved task definition must be the same object as the original task definition. The task definition cannot be changed.'); } - if (CustomExecution2DTO.is(resolvedTaskDTO.execution)) { - await this.addCustomExecution2(resolvedTaskDTO, resolvedTask, true); + if (CustomExecutionDTO.is(resolvedTaskDTO.execution)) { + await this.addCustomExecution(resolvedTaskDTO, resolvedTask, true); } return await this.resolveTaskInternal(resolvedTaskDTO); @@ -588,12 +588,12 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { return this._handleCounter++; } - protected async addCustomExecution2(taskDTO: tasks.TaskDTO, task: vscode.Task2, isProvided: boolean): Promise { + protected async addCustomExecution(taskDTO: tasks.TaskDTO, task: vscode.Task2, isProvided: boolean): Promise { const taskId = await this._proxy.$createTaskId(taskDTO); if (!isProvided && !this._providedCustomExecutions2.has(taskId)) { this._notProvidedCustomExecutions.add(taskId); } - this._providedCustomExecutions2.set(taskId, (task).execution2); + this._providedCustomExecutions2.set(taskId, (task).execution2); } protected async getTaskExecution(execution: tasks.TaskExecutionDTO | string, task?: vscode.Task): Promise { @@ -619,7 +619,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { } private customExecutionComplete(execution: tasks.TaskExecutionDTO): void { - const extensionCallback2: vscode.CustomExecution2 | undefined = this._activeCustomExecutions2.get(execution.id); + const extensionCallback2: vscode.CustomExecution | undefined = this._activeCustomExecutions2.get(execution.id); if (extensionCallback2) { this._activeCustomExecutions2.delete(execution.id); } @@ -674,8 +674,8 @@ export class WorkerExtHostTask extends ExtHostTaskBase { // If this task is a custom execution, then we need to save it away // in the provided custom execution map that is cleaned up after the // task is executed. - if (CustomExecution2DTO.is(dto.execution)) { - await this.addCustomExecution2(dto, task, false); + if (CustomExecutionDTO.is(dto.execution)) { + await this.addCustomExecution(dto, task, false); } else { throw new Error('Not implemented'); } @@ -692,12 +692,12 @@ export class WorkerExtHostTask extends ExtHostTaskBase { } const taskDTO: tasks.TaskDTO | undefined = TaskDTO.from(task, handler.extension); - if (taskDTO && CustomExecution2DTO.is(taskDTO.execution)) { + if (taskDTO && CustomExecutionDTO.is(taskDTO.execution)) { taskDTOs.push(taskDTO); // The ID is calculated on the main thread task side, so, let's call into it here. // We need the task id's pre-computed for custom task executions because when OnDidStartTask // is invoked, we have to be able to map it back to our data. - taskIdPromises.push(this.addCustomExecution2(taskDTO, task, true)); + taskIdPromises.push(this.addCustomExecution(taskDTO, task, true)); } else { console.warn('Only custom execution tasks supported.'); } @@ -710,7 +710,7 @@ export class WorkerExtHostTask extends ExtHostTaskBase { } protected async resolveTaskInternal(resolvedTaskDTO: tasks.TaskDTO): Promise { - if (CustomExecution2DTO.is(resolvedTaskDTO.execution)) { + if (CustomExecutionDTO.is(resolvedTaskDTO.execution)) { return resolvedTaskDTO; } else { console.warn('Only custom execution tasks supported.'); diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index 3b92133a9c..e01e7baa37 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -170,8 +170,8 @@ export interface TreeNode extends IDisposable { // {{SQL CARBON EDIT}} export in // {{SQL CARBON EDIT}} export class ExtHostTreeView extends Disposable { - private static LABEL_HANDLE_PREFIX = '0'; - private static ID_HANDLE_PREFIX = '1'; + private static readonly LABEL_HANDLE_PREFIX = '0'; + private static readonly ID_HANDLE_PREFIX = '1'; private readonly dataProvider: vscode.TreeDataProvider; @@ -557,7 +557,7 @@ export class ExtHostTreeView extends Disposable { private getLightIconPath(extensionTreeItem: vscode.TreeItem): URI | undefined { if (extensionTreeItem.iconPath && !(extensionTreeItem.iconPath instanceof ThemeIcon)) { if (typeof extensionTreeItem.iconPath === 'string' - || extensionTreeItem.iconPath instanceof URI) { + || URI.isUri(extensionTreeItem.iconPath)) { return this.getIconPath(extensionTreeItem.iconPath); } return this.getIconPath((<{ light: string | URI; dark: string | URI }>extensionTreeItem.iconPath).light); @@ -573,7 +573,7 @@ export class ExtHostTreeView extends Disposable { } private getIconPath(iconPath: string | URI): URI { - if (iconPath instanceof URI) { + if (URI.isUri(iconPath)) { return iconPath; } return URI.file(iconPath); diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 7a2e2c5073..2b9bb7da9f 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -255,12 +255,12 @@ export namespace MarkdownString { } // extract uris into a separate object - const resUris: { [href: string]: UriComponents } = Object.create(null); + const resUris: { [href: string]: UriComponents; } = Object.create(null); res.uris = resUris; const collectUri = (href: string): string => { try { - let uri = URI.parse(href, true); + let uri = URI.parse(href); uri = uri.with({ query: _uriMassage(uri.query, resUris) }); resUris[href] = uri; } catch (e) { @@ -277,13 +277,13 @@ export namespace MarkdownString { return res; } - function _uriMassage(part: string, bucket: { [n: string]: UriComponents }): string { + function _uriMassage(part: string, bucket: { [n: string]: UriComponents; }): string { if (!part) { return part; } let data: any; try { - data = parse(decodeURIComponent(part)); + data = parse(part); } catch (e) { // ignore } @@ -291,7 +291,7 @@ export namespace MarkdownString { return part; } data = cloneAndChange(data, value => { - if (value instanceof URI) { + if (URI.isUri(value)) { const key = `__uri_${Math.random().toString(16).slice(2, 8)}`; bucket[key] = value; return key; @@ -303,9 +303,7 @@ export namespace MarkdownString { } export function to(value: htmlContent.IMarkdownString): vscode.MarkdownString { - const ret = new htmlContent.MarkdownString(value.value); - ret.isTrusted = value.isTrusted; - return ret; + return new htmlContent.MarkdownString(value.value, value.isTrusted); } export function fromStrict(value: string | types.MarkdownString): undefined | string | htmlContent.IMarkdownString { @@ -515,7 +513,7 @@ export namespace WorkspaceEdit { export namespace SymbolKind { - const _fromMapping: { [kind: number]: modes.SymbolKind } = Object.create(null); + const _fromMapping: { [kind: number]: modes.SymbolKind; } = Object.create(null); _fromMapping[types.SymbolKind.File] = modes.SymbolKind.File; _fromMapping[types.SymbolKind.Module] = modes.SymbolKind.Module; _fromMapping[types.SymbolKind.Namespace] = modes.SymbolKind.Namespace; @@ -905,7 +903,7 @@ export namespace DocumentLink { let target: URI | undefined = undefined; if (link.url) { try { - target = typeof link.url === 'string' ? URI.parse(link.url, true) : URI.revive(link.url); + target = typeof link.url === 'string' ? URI.parse(link.url) : URI.revive(link.url); } catch (err) { // ignore } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 902377b40c..c28531703e 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -614,12 +614,7 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { } has(uri: URI): boolean { - for (const edit of this._edits) { - if (edit._type === 2 && edit.uri.toString() === uri.toString()) { - return true; - } - } - return false; + return this._edits.some(edit => edit._type === 2 && edit.uri.toString() === uri.toString()); } set(uri: URI, edits: TextEdit[]): void { @@ -1166,22 +1161,22 @@ export class CallHierarchyItem { export class CallHierarchyIncomingCall { - source: vscode.CallHierarchyItem; - sourceRanges: vscode.Range[]; + from: vscode.CallHierarchyItem; + fromRanges: vscode.Range[]; - constructor(item: vscode.CallHierarchyItem, sourceRanges: vscode.Range[]) { - this.sourceRanges = sourceRanges; - this.source = item; + constructor(item: vscode.CallHierarchyItem, fromRanges: vscode.Range[]) { + this.fromRanges = fromRanges; + this.from = item; } } export class CallHierarchyOutgoingCall { - target: vscode.CallHierarchyItem; - sourceRanges: vscode.Range[]; + to: vscode.CallHierarchyItem; + fromRanges: vscode.Range[]; - constructor(item: vscode.CallHierarchyItem, sourceRanges: vscode.Range[]) { - this.sourceRanges = sourceRanges; - this.target = item; + constructor(item: vscode.CallHierarchyItem, fromRanges: vscode.Range[]) { + this.fromRanges = fromRanges; + this.to = item; } } @@ -1227,7 +1222,9 @@ export class MarkdownString { appendText(value: string): MarkdownString { // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash - this.value += value.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&'); + this.value += value + .replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&') + .replace('\n', '\n\n'); return this; } @@ -1472,7 +1469,7 @@ export class DocumentLink { tooltip?: string; constructor(range: Range, target: URI | undefined) { - if (target && !(target instanceof URI)) { + if (target && !(URI.isUri(target))) { throw illegalArgument('target'); } if (!Range.isRange(range) || range.isEmpty) { @@ -1624,6 +1621,7 @@ export class ProcessExecution implements vscode.ProcessExecution { if (typeof process !== 'string') { throw illegalArgument('process'); } + this._args = []; this._process = process; if (varg1 !== undefined) { if (Array.isArray(varg1)) { @@ -1633,9 +1631,6 @@ export class ProcessExecution implements vscode.ProcessExecution { this._options = varg1; } } - if (this._args === undefined) { - this._args = []; - } } @@ -1687,9 +1682,9 @@ export class ProcessExecution implements vscode.ProcessExecution { @es5ClassCompat export class ShellExecution implements vscode.ShellExecution { - private _commandLine: string; - private _command: string | vscode.ShellQuotedString; - private _args: (string | vscode.ShellQuotedString)[]; + private _commandLine: string | undefined; + private _command: string | vscode.ShellQuotedString | undefined; + private _args: (string | vscode.ShellQuotedString)[] = []; private _options: vscode.ShellExecutionOptions | undefined; constructor(commandLine: string, options?: vscode.ShellExecutionOptions); @@ -1714,11 +1709,11 @@ export class ShellExecution implements vscode.ShellExecution { } } - get commandLine(): string { + get commandLine(): string | undefined { return this._commandLine; } - set commandLine(value: string) { + set commandLine(value: string | undefined) { if (typeof value !== 'string') { throw illegalArgument('commandLine'); } @@ -1726,7 +1721,7 @@ export class ShellExecution implements vscode.ShellExecution { } get command(): string | vscode.ShellQuotedString { - return this._command; + return this._command ? this._command : ''; } set command(value: string | vscode.ShellQuotedString) { @@ -1781,7 +1776,7 @@ export enum TaskScope { Workspace = 2 } -export class CustomExecution2 implements vscode.CustomExecution2 { +export class CustomExecution implements vscode.CustomExecution { private _callback: () => Thenable; constructor(callback: () => Thenable) { this._callback = callback; @@ -1812,7 +1807,7 @@ export class Task implements vscode.Task2 { private _definition: vscode.TaskDefinition; private _scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder | undefined; private _name: string; - private _execution: ProcessExecution | ShellExecution | CustomExecution2 | undefined; + private _execution: ProcessExecution | ShellExecution | CustomExecution | undefined; private _problemMatchers: string[]; private _hasDefinedMatchers: boolean; private _isBackground: boolean; @@ -1821,26 +1816,26 @@ export class Task implements vscode.Task2 { private _presentationOptions: vscode.TaskPresentationOptions; private _runOptions: vscode.RunOptions; - constructor(definition: vscode.TaskDefinition, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution2, problemMatchers?: string | string[]); - constructor(definition: vscode.TaskDefinition, scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution2, problemMatchers?: string | string[]); + constructor(definition: vscode.TaskDefinition, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution, problemMatchers?: string | string[]); + constructor(definition: vscode.TaskDefinition, scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution, problemMatchers?: string | string[]); constructor(definition: vscode.TaskDefinition, arg2: string | (vscode.TaskScope.Global | vscode.TaskScope.Workspace) | vscode.WorkspaceFolder, arg3: any, arg4?: any, arg5?: any, arg6?: any) { - this.definition = definition; + this._definition = this.definition = definition; let problemMatchers: string | string[]; if (typeof arg2 === 'string') { - this.name = arg2; - this.source = arg3; + this._name = this.name = arg2; + this._source = this.source = arg3; this.execution = arg4; problemMatchers = arg5; } else if (arg2 === TaskScope.Global || arg2 === TaskScope.Workspace) { this.target = arg2; - this.name = arg3; - this.source = arg4; + this._name = this.name = arg3; + this._source = this.source = arg4; this.execution = arg5; problemMatchers = arg6; } else { this.target = arg2; - this.name = arg3; - this.source = arg4; + this._name = this.name = arg3; + this._source = this.source = arg4; this.execution = arg5; problemMatchers = arg6; } @@ -1887,7 +1882,7 @@ export class Task implements vscode.Task2 { type: Task.ShellType, id: this._execution.computeId() }; - } else if (this._execution instanceof CustomExecution2) { + } else if (this._execution instanceof CustomExecution) { this._definition = { type: Task.ExtensionCallbackType, id: this._execution.computeId() @@ -1934,18 +1929,18 @@ export class Task implements vscode.Task2 { } get execution(): ProcessExecution | ShellExecution | undefined { - return (this._execution instanceof CustomExecution2) ? undefined : this._execution; + return (this._execution instanceof CustomExecution) ? undefined : this._execution; } set execution(value: ProcessExecution | ShellExecution | undefined) { this.execution2 = value; } - get execution2(): ProcessExecution | ShellExecution | CustomExecution2 | undefined { + get execution2(): ProcessExecution | ShellExecution | CustomExecution | undefined { return this._execution; } - set execution2(value: ProcessExecution | ShellExecution | CustomExecution2 | undefined) { + set execution2(value: ProcessExecution | ShellExecution | CustomExecution | undefined) { if (value === null) { value = undefined; } @@ -2059,7 +2054,7 @@ export class TreeItem { constructor(label: string | vscode.TreeItemLabel, collapsibleState?: vscode.TreeItemCollapsibleState) constructor(resourceUri: URI, collapsibleState?: vscode.TreeItemCollapsibleState) constructor(arg1: string | vscode.TreeItemLabel | URI, public collapsibleState: vscode.TreeItemCollapsibleState = TreeItemCollapsibleState.None) { - if (arg1 instanceof URI) { + if (URI.isUri(arg1)) { this.resourceUri = arg1; } else { this.label = arg1; diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index fa8880fd28..a89670b110 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -7,8 +7,9 @@ import { Emitter, Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import * as modes from 'vs/editor/common/modes'; -import { IExtensionDescription, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; +import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; import * as vscode from 'vscode'; @@ -18,7 +19,7 @@ import { Disposable } from './extHostTypes'; type IconPath = URI | { light: URI, dark: URI }; export class ExtHostWebview implements vscode.Webview { - private _html: string; + private _html: string = ''; private _isDisposed: boolean = false; private _hasCalledAsWebviewUri = false; @@ -30,7 +31,8 @@ export class ExtHostWebview implements vscode.Webview { private readonly _proxy: MainThreadWebviewsShape, private _options: vscode.WebviewOptions, private readonly _initData: WebviewInitData, - private readonly _extensionId: ExtensionIdentifier | undefined, + private readonly _workspace: IExtHostWorkspace | undefined, + private readonly _extension: IExtensionDescription, ) { } public dispose() { @@ -56,10 +58,10 @@ export class ExtHostWebview implements vscode.Webview { this.assertNotDisposed(); if (this._html !== value) { this._html = value; - if (this._initData.isExtensionDevelopmentDebug && this._extensionId && !this._hasCalledAsWebviewUri) { + if (this._initData.isExtensionDevelopmentDebug && !this._hasCalledAsWebviewUri) { if (/(["'])vscode-resource:([^\s'"]+?)(["'])/i.test(value)) { this._hasCalledAsWebviewUri = true; - console.warn(`${this._extensionId.value} created a webview that appears to use the vscode-resource scheme directly. Please migrate to use the 'webview.asWebviewUri' api instead: https://aka.ms/vscode-webview-use-aswebviewuri`); + console.warn(`${this._extension.identifier.value} created a webview that appears to use the vscode-resource scheme directly. Please migrate to use the 'webview.asWebviewUri' api instead: https://aka.ms/vscode-webview-use-aswebviewuri`); } } this._proxy.$setHtml(this._handle, value); @@ -73,7 +75,7 @@ export class ExtHostWebview implements vscode.Webview { public set options(newOptions: vscode.WebviewOptions) { this.assertNotDisposed(); - this._proxy.$setOptions(this._handle, convertWebviewOptions(newOptions)); + this._proxy.$setOptions(this._handle, convertWebviewOptions(this._extension, this._workspace, newOptions)); this._options = newOptions; } @@ -261,12 +263,13 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { private readonly _proxy: MainThreadWebviewsShape; private readonly _webviewPanels = new Map(); - private readonly _serializers = new Map(); + private readonly _serializers = new Map(); private readonly _editorProviders = new Map(); constructor( mainContext: IMainContext, - private readonly initData: WebviewInitData + private readonly initData: WebviewInitData, + private readonly workspace: IExtHostWorkspace | undefined, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadWebviews); } @@ -285,15 +288,16 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { }; const handle = ExtHostWebviews.newHandle(); - this._proxy.$createWebviewPanel(handle, viewType, title, webviewShowOptions, convertWebviewOptions(options), extension.identifier, extension.extensionLocation); + this._proxy.$createWebviewPanel({ id: extension.identifier, location: extension.extensionLocation }, handle, viewType, title, webviewShowOptions, convertWebviewOptions(extension, this.workspace, options)); - const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, extension.identifier); + const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension); const panel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, viewColumn, options, webview); this._webviewPanels.set(handle, panel); return panel; } public registerWebviewPanelSerializer( + extension: IExtensionDescription, viewType: string, serializer: vscode.WebviewPanelSerializer ): vscode.Disposable { @@ -301,7 +305,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { throw new Error(`Serializer for '${viewType}' already registered`); } - this._serializers.set(viewType, serializer); + this._serializers.set(viewType, { serializer, extension }); this._proxy.$registerSerializer(viewType); return new Disposable(() => { @@ -314,13 +318,14 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { extension: IExtensionDescription, viewType: string, provider: vscode.WebviewEditorProvider, + options?: vscode.WebviewPanelOptions, ): vscode.Disposable { if (this._editorProviders.has(viewType)) { throw new Error(`Editor provider for '${viewType}' already registered`); } this._editorProviders.set(viewType, { extension, provider, }); - this._proxy.$registerEditorProvider(viewType); + this._proxy.$registerEditorProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, options || {}); return new Disposable(() => { this._editorProviders.delete(viewType); @@ -397,12 +402,13 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions ): Promise { - const serializer = this._serializers.get(viewType); - if (!serializer) { + const entry = this._serializers.get(viewType); + if (!entry) { return Promise.reject(new Error(`No serializer found for '${viewType}'`)); } + const { serializer, extension } = entry; - const webview = new ExtHostWebview(webviewHandle, this._proxy, options, this.initData, undefined); + const webview = new ExtHostWebview(webviewHandle, this._proxy, options, this.initData, this.workspace, extension); const revivedPanel = new ExtHostWebviewEditor(webviewHandle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview); this._webviewPanels.set(webviewHandle, revivedPanel); return Promise.resolve(serializer.deserializeWebviewPanel(revivedPanel, state)); @@ -417,7 +423,6 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { handle: WebviewPanelHandle, viewType: string, title: string, - state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions ): Promise { @@ -426,10 +431,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { return Promise.reject(new Error(`No provider found for '${viewType}'`)); } const { provider, extension } = entry; - - this._proxy.$setExtension(handle, extension.identifier, extension.extensionLocation); - - const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, extension.identifier); + const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension); const revivedPanel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview); this._webviewPanels.set(handle, revivedPanel); return Promise.resolve(provider.resolveWebviewEditor(URI.revive(resource), revivedPanel)); @@ -445,18 +447,22 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } function convertWebviewOptions( - options: vscode.WebviewPanelOptions & vscode.WebviewOptions + extension: IExtensionDescription, + workspace: IExtHostWorkspace | undefined, + options: vscode.WebviewPanelOptions & vscode.WebviewOptions, ): modes.IWebviewOptions { return { ...options, - portMapping: options.portMapping - ? options.portMapping.map((x): modes.IWebviewPortMapping => { - // Handle old proposed api - if ('port' in x) { - return { webviewPort: (x as any).port, extensionHostPort: (x as any).resolvedPort }; - } - return { webviewPort: x.webviewPort, extensionHostPort: x.extensionHostPort }; - }) - : undefined, + localResourceRoots: options.localResourceRoots || getDefaultLocalResourceRoots(extension, workspace) }; } + +function getDefaultLocalResourceRoots( + extension: IExtensionDescription, + workspace: IExtHostWorkspace | undefined, +): URI[] { + return [ + ...(workspace && workspace.getWorkspaceFolders() || []).map(x => x.uri), + extension.extensionLocation, + ]; +} diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 3d3b9e270f..f4427b6035 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -12,7 +12,7 @@ import { IExtensionPointUser, ExtensionMessageCollector, ExtensionsRegistry } fr import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { MenuId, MenuRegistry, ILocalizedString, IMenuItem } from 'vs/platform/actions/common/actions'; import { URI } from 'vs/base/common/uri'; -import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; namespace schema { @@ -392,7 +392,7 @@ commandsExtensionPoint.setHandler(extensions => { } }); -let _menuRegistrations: IDisposable[] = []; +const _menuRegistrations = new DisposableStore(); ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyMenuItem[] }>({ extensionPoint: 'menus', @@ -400,7 +400,7 @@ ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyM }).setHandler(extensions => { // remove all previous menu registrations - _menuRegistrations = dispose(_menuRegistrations); + _menuRegistrations.clear(); for (let extension of extensions) { const { value, collector } = extension; @@ -455,7 +455,7 @@ ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyM order, when: ContextKeyExpr.deserialize(item.when) } as IMenuItem); - _menuRegistrations.push(registration); + _menuRegistrations.add(registration); } }); } diff --git a/src/vs/workbench/api/common/shared/tasks.ts b/src/vs/workbench/api/common/shared/tasks.ts index e4b52590e6..4444283185 100644 --- a/src/vs/workbench/api/common/shared/tasks.ts +++ b/src/vs/workbench/api/common/shared/tasks.ts @@ -66,8 +66,8 @@ export interface ShellExecutionDTO { options?: ShellExecutionOptionsDTO; } -export interface CustomExecution2DTO { - customExecution: 'customExecution2'; +export interface CustomExecutionDTO { + customExecution: 'customExecution'; } export interface TaskSourceDTO { @@ -84,7 +84,7 @@ export interface TaskHandleDTO { export interface TaskDTO { _id: string; name?: string; - execution: ProcessExecutionDTO | ShellExecutionDTO | CustomExecution2DTO | undefined; + execution: ProcessExecutionDTO | ShellExecutionDTO | CustomExecutionDTO | undefined; definition: TaskDefinitionDTO; isBackground?: boolean; source: TaskSourceDTO; diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 9db6409bf3..a77f96210b 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -82,12 +82,12 @@ export class ExtHostDebugService implements IExtHostDebugService, ExtHostDebugSe private _debugAdapters: Map; private _debugAdaptersTrackers: Map; - private _variableResolver: IConfigurationResolverService; + private _variableResolver: IConfigurationResolverService | undefined; private _integratedTerminalInstance?: vscode.Terminal; - private _terminalDisposedListener: IDisposable; + private _terminalDisposedListener: IDisposable | undefined; - private _signService: ISignService; + private _signService: ISignService | undefined; constructor( @@ -1070,10 +1070,8 @@ interface IDapTransport { class DirectDebugAdapter extends AbstractDebugAdapter implements IDapTransport { - readonly onError: Event; - readonly onExit: Event; - private _sendUp: (msg: DebugProtocol.ProtocolMessage) => void; + private _sendUp!: (msg: DebugProtocol.ProtocolMessage) => void; constructor(implementation: any) { super(); diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index 07c6082d59..3a97868f54 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -19,7 +19,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; -import { ExtHostTaskBase, TaskHandleDTO, TaskDTO, CustomExecution2DTO, HandlerData } from 'vs/workbench/api/common/extHostTask'; +import { ExtHostTaskBase, TaskHandleDTO, TaskDTO, CustomExecutionDTO, HandlerData } from 'vs/workbench/api/common/extHostTask'; import { Schemas } from 'vs/base/common/network'; export class ExtHostTask extends ExtHostTaskBase { @@ -56,8 +56,8 @@ export class ExtHostTask extends ExtHostTaskBase { // If this task is a custom execution, then we need to save it away // in the provided custom execution map that is cleaned up after the // task is executed. - if (CustomExecution2DTO.is(dto.execution)) { - await this.addCustomExecution2(dto, task, false); + if (CustomExecutionDTO.is(dto.execution)) { + await this.addCustomExecution(dto, task, false); } return this._proxy.$executeTask(dto).then(value => this.getTaskExecution(value, task)); @@ -76,11 +76,11 @@ export class ExtHostTask extends ExtHostTaskBase { if (taskDTO) { taskDTOs.push(taskDTO); - if (CustomExecution2DTO.is(taskDTO.execution)) { + if (CustomExecutionDTO.is(taskDTO.execution)) { // The ID is calculated on the main thread task side, so, let's call into it here. // We need the task id's pre-computed for custom task executions because when OnDidStartTask // is invoked, we have to be able to map it back to our data. - taskIdPromises.push(this.addCustomExecution2(taskDTO, task, true)); + taskIdPromises.push(this.addCustomExecution(taskDTO, task, true)); } } } diff --git a/src/vs/workbench/api/worker/extHostExtensionService.ts b/src/vs/workbench/api/worker/extHostExtensionService.ts index 3db847ee1b..39bf16efc4 100644 --- a/src/vs/workbench/api/worker/extHostExtensionService.ts +++ b/src/vs/workbench/api/worker/extHostExtensionService.ts @@ -32,7 +32,7 @@ class WorkerRequireInterceptor extends RequireInterceptor { export class ExtHostExtensionService extends AbstractExtHostExtensionService { - private _fakeModules: WorkerRequireInterceptor; + private _fakeModules?: WorkerRequireInterceptor; protected async _beforeAlmostReadyToRunExtensions(): Promise { // initialize API and register actors @@ -57,7 +57,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { const _exports = {}; const _module = { exports: _exports }; const _require = (request: string) => { - const result = this._fakeModules.getModule(request, module); + const result = this._fakeModules!.getModule(request, module); if (result === undefined) { throw new Error(`Cannot load module '${request}'`); } diff --git a/src/vs/workbench/browser/actions.ts b/src/vs/workbench/browser/actions.ts index c8200d1876..e796de7bcb 100644 --- a/src/vs/workbench/browser/actions.ts +++ b/src/vs/workbench/browser/actions.ts @@ -57,13 +57,7 @@ export class ContributableActionProvider implements IActionProvider { const context = this.toContext(tree, element); const contributors = this.registry.getActionBarContributors(Scope.VIEWER); - for (const contributor of contributors) { - if (contributor.hasActions(context)) { - return true; - } - } - - return false; + return contributors.some(contributor => contributor.hasActions(context)); } getActions(tree: ITree, element: unknown): ReadonlyArray { @@ -156,7 +150,7 @@ export interface IActionBarRegistry { class ActionBarRegistry implements IActionBarRegistry { private readonly actionBarContributorConstructors: { scope: string; ctor: IConstructorSignature0; }[] = []; private readonly actionBarContributorInstances: Map = new Map(); - private instantiationService!: IInstantiationService; + private instantiationService: IInstantiationService | undefined; start(accessor: ServicesAccessor): void { this.instantiationService = accessor.get(IInstantiationService); @@ -168,13 +162,15 @@ class ActionBarRegistry implements IActionBarRegistry { } private createActionBarContributor(scope: string, ctor: IConstructorSignature0): void { - const instance = this.instantiationService.createInstance(ctor); - let target = this.actionBarContributorInstances.get(scope); - if (!target) { - target = []; - this.actionBarContributorInstances.set(scope, target); + if (this.instantiationService) { + const instance = this.instantiationService.createInstance(ctor); + let target = this.actionBarContributorInstances.get(scope); + if (!target) { + target = []; + this.actionBarContributorInstances.set(scope, target); + } + target.push(instance); } - target.push(instance); } private getContributors(scope: string): ActionBarContributor[] { diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index e4f5b43deb..db02240757 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -29,7 +29,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'v class InspectContextKeysAction extends Action { static readonly ID = 'workbench.action.inspectContextKeys'; - static LABEL = nls.localize('inspect context keys', "Inspect Context Keys"); + static readonly LABEL = nls.localize('inspect context keys', "Inspect Context Keys"); constructor( id: string, @@ -91,7 +91,7 @@ class InspectContextKeysAction extends Action { class ToggleScreencastModeAction extends Action { static readonly ID = 'workbench.action.toggleScreencastMode'; - static LABEL = nls.localize('toggle screencast mode', "Toggle Screencast Mode"); + static readonly LABEL = nls.localize('toggle screencast mode', "Toggle Screencast Mode"); static disposable: IDisposable | undefined; @@ -195,7 +195,7 @@ class ToggleScreencastModeAction extends Action { class LogStorageAction extends Action { static readonly ID = 'workbench.action.logStorage'; - static LABEL = nls.localize({ key: 'logStorage', comment: ['A developer only action to log the contents of the storage for the current window.'] }, "Log Storage Database Contents"); + static readonly LABEL = nls.localize({ key: 'logStorage', comment: ['A developer only action to log the contents of the storage for the current window.'] }, "Log Storage Database Contents"); constructor( id: string, diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 80ee997a4a..fc21cc0785 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -16,13 +16,14 @@ import { IEditorGroupsService, GroupOrientation } from 'vs/workbench/services/ed import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { MenuBarVisibility } from 'vs/platform/windows/common/windows'; +import { getMenuBarVisibility } from 'vs/platform/windows/common/windows'; import { isWindows, isLinux, isWeb } from 'vs/base/common/platform'; import { IsMacNativeContext } from 'vs/workbench/browser/contextkeys'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { InEditorZenModeContext, IsCenteredLayoutContext } from 'vs/workbench/common/editor'; +import { InEditorZenModeContext, IsCenteredLayoutContext, EditorAreaVisibleContext } from 'vs/workbench/common/editor'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { SideBarVisibleContext } from 'vs/workbench/common/viewlet'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; const registry = Registry.as(Extensions.WorkbenchActions); const viewCategory = nls.localize('view', "View"); @@ -232,6 +233,16 @@ export class ToggleEditorVisibilityAction extends Action { registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleEditorVisibilityAction, ToggleEditorVisibilityAction.ID, ToggleEditorVisibilityAction.LABEL), 'View: Toggle Editor Area Visibility', viewCategory); +MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + group: '2_workbench_layout', + command: { + id: ToggleEditorVisibilityAction.ID, + title: nls.localize({ key: 'miShowEditorArea', comment: ['&& denotes a mnemonic'] }, "Show &&Editor Area"), + toggled: EditorAreaVisibleContext + }, + order: 5 +}); + export class ToggleSidebarVisibilityAction extends Action { static readonly ID = 'workbench.action.toggleSidebarVisibility'; @@ -340,7 +351,7 @@ class ToggleTabsVisibilityAction extends Action { } registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleTabsVisibilityAction, ToggleTabsVisibilityAction.ID, ToggleTabsVisibilityAction.LABEL, { - primary: undefined!, + primary: undefined, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_W, }, linux: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_W, } }), 'View: Toggle Tab Visibility', viewCategory); @@ -396,20 +407,21 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ export class ToggleMenuBarAction extends Action { static readonly ID = 'workbench.action.toggleMenuBar'; - static LABEL = nls.localize('toggleMenuBar', "Toggle Menu Bar"); + static readonly LABEL = nls.localize('toggleMenuBar', "Toggle Menu Bar"); private static readonly menuBarVisibilityKey = 'window.menuBarVisibility'; constructor( id: string, label: string, - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IEnvironmentService private readonly environmentService: IEnvironmentService ) { super(id, label); } run(): Promise { - let currentVisibilityValue = this.configurationService.getValue(ToggleMenuBarAction.menuBarVisibilityKey); + let currentVisibilityValue = getMenuBarVisibility(this.configurationService, this.environmentService); if (typeof currentVisibilityValue !== 'string') { currentVisibilityValue = 'default'; } @@ -417,8 +429,10 @@ export class ToggleMenuBarAction extends Action { let newVisibilityValue: string; if (currentVisibilityValue === 'visible' || currentVisibilityValue === 'default') { newVisibilityValue = 'toggle'; + } else if (currentVisibilityValue === 'compact') { + newVisibilityValue = 'hidden'; } else { - newVisibilityValue = 'default'; + newVisibilityValue = (isWeb && currentVisibilityValue === 'hidden') ? 'compact' : 'default'; } this.configurationService.updateValue(ToggleMenuBarAction.menuBarVisibilityKey, newVisibilityValue, ConfigurationTarget.USER); @@ -446,7 +460,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { export abstract class BaseResizeViewAction extends Action { - protected static RESIZE_INCREMENT = 6.5; // This is a media-size percentage + protected static readonly RESIZE_INCREMENT = 6.5; // This is a media-size percentage constructor( id: string, diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index 364c138fae..db5f82dbc4 100644 --- a/src/vs/workbench/browser/actions/listCommands.ts +++ b/src/vs/workbench/browser/actions/listCommands.ts @@ -17,6 +17,11 @@ import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; import { DataTree } from 'vs/base/browser/ui/tree/dataTree'; import { ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; + +function isLegacyTree(widget: ListWidget): widget is ITree { + return widget instanceof Tree; +} function ensureDOMFocus(widget: ListWidget | undefined): void { // it can happen that one of the commands is executed while @@ -552,7 +557,7 @@ function listFocusFirst(accessor: ServicesAccessor, options?: { fromFocused: boo else if (focused) { const tree = focused; - tree.focusFirst({ origin: 'keyboard' }, options && options.fromFocused ? tree.getFocus() : undefined); + tree.focusFirst({ origin: 'keyboard' }, options?.fromFocused ? tree.getFocus() : undefined); tree.reveal(tree.getFocus()); } } @@ -604,7 +609,7 @@ function listFocusLast(accessor: ServicesAccessor, options?: { fromFocused: bool else if (focused) { const tree = focused; - tree.focusLast({ origin: 'keyboard' }, options && options.fromFocused ? tree.getFocus() : undefined); + tree.focusLast({ origin: 'keyboard' }, options?.fromFocused ? tree.getFocus() : undefined); tree.reveal(tree.getFocus()); } } @@ -833,3 +838,67 @@ CommandsRegistry.registerCommand({ } } }); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'list.scrollUp', + weight: KeybindingWeight.WorkbenchContrib, + when: WorkbenchListFocusContextKey, + primary: KeyMod.CtrlCmd | KeyCode.UpArrow, + handler: accessor => { + const focused = accessor.get(IListService).lastFocusedList; + + if (!focused || isLegacyTree(focused)) { + return; + } + + focused.scrollTop -= 10; + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'list.scrollDown', + weight: KeybindingWeight.WorkbenchContrib, + when: WorkbenchListFocusContextKey, + primary: KeyMod.CtrlCmd | KeyCode.DownArrow, + handler: accessor => { + const focused = accessor.get(IListService).lastFocusedList; + + if (!focused || isLegacyTree(focused)) { + return; + } + + focused.scrollTop += 10; + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'list.scrollLeft', + weight: KeybindingWeight.WorkbenchContrib, + when: WorkbenchListFocusContextKey, + primary: KeyMod.CtrlCmd | KeyCode.LeftArrow, + handler: accessor => { + const focused = accessor.get(IListService).lastFocusedList; + + if (!focused || isLegacyTree(focused)) { + return; + } + + focused.scrollLeft -= 10; + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'list.scrollRight', + weight: KeybindingWeight.WorkbenchContrib, + when: WorkbenchListFocusContextKey, + primary: KeyMod.CtrlCmd | KeyCode.RightArrow, + handler: accessor => { + const focused = accessor.get(IListService).lastFocusedList; + + if (!focused || isLegacyTree(focused)) { + return; + } + + focused.scrollLeft += 10; + } +}); diff --git a/src/vs/workbench/browser/actions/navigationActions.ts b/src/vs/workbench/browser/actions/navigationActions.ts index e9908913d3..d697091687 100644 --- a/src/vs/workbench/browser/actions/navigationActions.ts +++ b/src/vs/workbench/browser/actions/navigationActions.ts @@ -68,9 +68,19 @@ abstract class BaseNavigationAction extends Action { return false; } - const activePanelId = this.panelService.getActivePanel()!.getId(); + const activePanel = this.panelService.getActivePanel(); + if (!activePanel) { + return false; + } - return this.panelService.openPanel(activePanelId, true)!; + const activePanelId = activePanel.getId(); + + const res = this.panelService.openPanel(activePanelId, true); + if (!res) { + return false; + } + + return res; } protected async navigateToSidebar(): Promise { @@ -84,8 +94,8 @@ abstract class BaseNavigationAction extends Action { } const activeViewletId = activeViewlet.getId(); - const value = await this.viewletService.openViewlet(activeViewletId, true); - return value === null ? false : value; + const viewlet = await this.viewletService.openViewlet(activeViewletId, true); + return !!viewlet; } protected navigateAcrossEditorGroup(direction: GroupDirection): boolean { diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index 35fee36b12..bc5a96f7f7 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -37,7 +37,7 @@ export const inRecentFilesPickerContextKey = 'inRecentFilesPicker'; abstract class BaseOpenRecentAction extends Action { private removeFromRecentlyOpened: IQuickInputButton = { - iconClass: 'action-remove-from-recently-opened', + iconClass: 'codicon-close', tooltip: nls.localize('remove', "Remove from Recently Opened") }; @@ -135,7 +135,7 @@ abstract class BaseOpenRecentAction extends Action { }); if (pick) { - return this.hostService.openWindow([pick.openable], { forceNewWindow: keyMods && keyMods.ctrlCmd }); + return this.hostService.openWindow([pick.openable], { forceNewWindow: keyMods?.ctrlCmd }); } } } @@ -193,7 +193,7 @@ class QuickOpenRecentAction extends BaseOpenRecentAction { class ToggleFullScreenAction extends Action { static readonly ID = 'workbench.action.toggleFullScreen'; - static LABEL = nls.localize('toggleFullScreen', "Toggle Full Screen"); + static readonly LABEL = nls.localize('toggleFullScreen', "Toggle Full Screen"); constructor( id: string, @@ -211,7 +211,7 @@ class ToggleFullScreenAction extends Action { export class ReloadWindowAction extends Action { static readonly ID = 'workbench.action.reloadWindow'; - static LABEL = nls.localize('reloadWindow', "Reload Window"); + static readonly LABEL = nls.localize('reloadWindow', "Reload Window"); constructor( id: string, @@ -249,7 +249,7 @@ class ShowAboutDialogAction extends Action { export class NewWindowAction extends Action { static readonly ID = 'workbench.action.newWindow'; - static LABEL = nls.localize('newWindow', "New Window"); + static readonly LABEL = nls.localize('newWindow', "New Window"); constructor( id: string, diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index a050926693..f36e93fca7 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -21,11 +21,12 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IHostService } from 'vs/workbench/services/host/browser/host'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export class OpenFileAction extends Action { static readonly ID = 'workbench.action.files.openFile'; - static LABEL = nls.localize('openFile', "Open File..."); + static readonly LABEL = nls.localize('openFile', "Open File..."); constructor( id: string, @@ -43,7 +44,7 @@ export class OpenFileAction extends Action { export class OpenFolderAction extends Action { static readonly ID = 'workbench.action.files.openFolder'; - static LABEL = nls.localize('openFolder', "Open Folder..."); + static readonly LABEL = nls.localize('openFolder', "Open Folder..."); constructor( id: string, @@ -61,7 +62,7 @@ export class OpenFolderAction extends Action { export class OpenFileFolderAction extends Action { static readonly ID = 'workbench.action.files.openFileFolder'; - static LABEL = nls.localize('openFileFolder', "Open..."); + static readonly LABEL = nls.localize('openFileFolder', "Open..."); constructor( id: string, @@ -79,7 +80,7 @@ export class OpenFileFolderAction extends Action { export class OpenWorkspaceAction extends Action { static readonly ID = 'workbench.action.openWorkspace'; - static LABEL = nls.localize('openWorkspaceAction', "Open Workspace..."); + static readonly LABEL = nls.localize('openWorkspaceAction', "Open Workspace..."); constructor( id: string, @@ -97,14 +98,15 @@ export class OpenWorkspaceAction extends Action { export class CloseWorkspaceAction extends Action { static readonly ID = 'workbench.action.closeFolder'; - static LABEL = nls.localize('closeWorkspace', "Close Workspace"); + static readonly LABEL = nls.localize('closeWorkspace', "Close Workspace"); constructor( id: string, label: string, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @INotificationService private readonly notificationService: INotificationService, - @IHostService private readonly hostService: IHostService + @IHostService private readonly hostService: IHostService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService ) { super(id, label); } @@ -116,7 +118,7 @@ export class CloseWorkspaceAction extends Action { return Promise.resolve(undefined); } - return this.hostService.closeWorkspace(); + return this.hostService.openWindow({ forceReuseWindow: true, remoteAuthority: this.environmentService.configuration.remoteAuthority }); } } @@ -148,7 +150,7 @@ export class OpenWorkspaceConfigFileAction extends Action { export class AddRootFolderAction extends Action { static readonly ID = 'workbench.action.addRootFolder'; - static LABEL = ADD_ROOT_FOLDER_LABEL; + static readonly LABEL = ADD_ROOT_FOLDER_LABEL; constructor( id: string, @@ -166,7 +168,7 @@ export class AddRootFolderAction extends Action { export class GlobalRemoveRootFolderAction extends Action { static readonly ID = 'workbench.action.removeRootFolder'; - static LABEL = nls.localize('globalRemoveFolderFromWorkspace', "Remove Folder from Workspace..."); + static readonly LABEL = nls.localize('globalRemoveFolderFromWorkspace', "Remove Folder from Workspace..."); constructor( id: string, diff --git a/src/vs/workbench/browser/composite.ts b/src/vs/workbench/browser/composite.ts index 5d6e447e54..a472d79a12 100644 --- a/src/vs/workbench/browser/composite.ts +++ b/src/vs/workbench/browser/composite.ts @@ -14,6 +14,8 @@ import { IConstructorSignature0, IInstantiationService } from 'vs/platform/insta import { trackFocus, Dimension } from 'vs/base/browser/dom'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { Disposable } from 'vs/base/common/lifecycle'; +import { assertIsDefined } from 'vs/base/common/types'; +import { find } from 'vs/base/common/arrays'; /** * Composites are layed out in the sidebar and panel part of the workbench. At a time only one composite @@ -35,10 +37,10 @@ export abstract class Composite extends Component implements IComposite { private readonly _onDidChangeVisibility: Emitter = this._register(new Emitter()); readonly onDidChangeVisibility: Event = this._onDidChangeVisibility.event; - private _onDidFocus!: Emitter; + private _onDidFocus: Emitter | undefined; get onDidFocus(): Event { if (!this._onDidFocus) { - this.registerFocusTrackEvents(); + this._onDidFocus = this.registerFocusTrackEvents().onDidFocus; } return this._onDidFocus.event; @@ -50,28 +52,32 @@ export abstract class Composite extends Component implements IComposite { } } - private _onDidBlur!: Emitter; + private _onDidBlur: Emitter | undefined; get onDidBlur(): Event { if (!this._onDidBlur) { - this.registerFocusTrackEvents(); + this._onDidBlur = this.registerFocusTrackEvents().onDidBlur; } return this._onDidBlur.event; } - private registerFocusTrackEvents(): void { - this._onDidFocus = this._register(new Emitter()); - this._onDidBlur = this._register(new Emitter()); + private registerFocusTrackEvents(): { onDidFocus: Emitter, onDidBlur: Emitter } { + const container = assertIsDefined(this.getContainer()); + const focusTracker = this._register(trackFocus(container)); - const focusTracker = this._register(trackFocus(this.getContainer())); - this._register(focusTracker.onDidFocus(() => this._onDidFocus.fire())); - this._register(focusTracker.onDidBlur(() => this._onDidBlur.fire())); + const onDidFocus = this._onDidFocus = this._register(new Emitter()); + this._register(focusTracker.onDidFocus(() => onDidFocus.fire())); + + const onDidBlur = this._onDidBlur = this._register(new Emitter()); + this._register(focusTracker.onDidBlur(() => onDidBlur.fire())); + + return { onDidFocus, onDidBlur }; } protected actionRunner: IActionRunner | undefined; private visible: boolean; - private parent!: HTMLElement; + private parent: HTMLElement | undefined; constructor( id: string, @@ -112,7 +118,7 @@ export abstract class Composite extends Component implements IComposite { /** * Returns the container this composite is being build in. */ - getContainer(): HTMLElement { + getContainer(): HTMLElement | undefined { return this.parent; } @@ -253,7 +259,7 @@ export abstract class CompositeRegistry extends Disposable private composites: CompositeDescriptor[] = []; protected registerComposite(descriptor: CompositeDescriptor): void { - if (this.compositeById(descriptor.id) !== null) { + if (this.compositeById(descriptor.id)) { return; } @@ -271,7 +277,7 @@ export abstract class CompositeRegistry extends Disposable this._onDidDeregister.fire(descriptor); } - getComposite(id: string): CompositeDescriptor | null { + getComposite(id: string): CompositeDescriptor | undefined { return this.compositeById(id); } @@ -279,13 +285,7 @@ export abstract class CompositeRegistry extends Disposable return this.composites.slice(0); } - private compositeById(id: string): CompositeDescriptor | null { - for (const composite of this.composites) { - if (composite.id === id) { - return composite; - } - } - - return null; + private compositeById(id: string): CompositeDescriptor | undefined { + return find(this.composites, composite => composite.id === id); } } diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index 1498390c80..bce987e2a5 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys'; import { IWindowsConfiguration } from 'vs/platform/windows/common/windows'; -import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorIsSaveableContext, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; +import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorIsSaveableContext, toResource, SideBySideEditor, EditorAreaVisibleContext } 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'; @@ -73,6 +73,7 @@ export class WorkbenchContextKeysHandler extends Disposable { private isFullscreenContext: IContextKey; private isCenteredLayoutContext: IContextKey; private sideBarVisibleContext: IContextKey; + private editorAreaVisibleContext: IContextKey; private panelPositionContext: IContextKey; constructor( @@ -88,41 +89,6 @@ export class WorkbenchContextKeysHandler extends Disposable { ) { super(); - this.initContextKeys(); - this.registerListeners(); - } - - private registerListeners(): void { - this.editorGroupService.whenRestored.then(() => this.updateEditorContextKeys()); - - this._register(this.editorService.onDidActiveEditorChange(() => this.updateEditorContextKeys())); - this._register(this.editorService.onDidVisibleEditorsChange(() => this.updateEditorContextKeys())); - - this._register(this.editorGroupService.onDidAddGroup(() => this.updateEditorContextKeys())); - this._register(this.editorGroupService.onDidRemoveGroup(() => this.updateEditorContextKeys())); - this._register(this.editorGroupService.onDidGroupIndexChange(() => this.updateEditorContextKeys())); - - this._register(addDisposableListener(window, EventType.FOCUS_IN, () => this.updateInputContextKeys(), true)); - - this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateWorkbenchStateContextKey())); - this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.updateWorkspaceFolderCountContextKey())); - - this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('workbench.editor.openSideBySideDirection')) { - this.updateSplitEditorsVerticallyContext(); - } - })); - - 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())); - } - - private initContextKeys(): void { // Platform IsMacContext.bindTo(this.contextKeyService); @@ -136,7 +102,7 @@ export class WorkbenchContextKeysHandler extends Disposable { // macOS Native Tabs const windowConfig = this.configurationService.getValue(); - HasMacNativeTabsContext.bindTo(this.contextKeyService).set(windowConfig && windowConfig.window && windowConfig.window.nativeTabs); + HasMacNativeTabsContext.bindTo(this.contextKeyService).set(windowConfig?.window?.nativeTabs); // Development IsDevelopmentContext.bindTo(this.contextKeyService).set(!this.environmentService.isBuilt || this.environmentService.isExtensionDevelopment); @@ -181,12 +147,49 @@ export class WorkbenchContextKeysHandler extends Disposable { // Centered Layout this.isCenteredLayoutContext = IsCenteredLayoutContext.bindTo(this.contextKeyService); + // Editor Area + this.editorAreaVisibleContext = EditorAreaVisibleContext.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'); + + this.registerListeners(); + } + + private registerListeners(): void { + this.editorGroupService.whenRestored.then(() => this.updateEditorContextKeys()); + + this._register(this.editorService.onDidActiveEditorChange(() => this.updateEditorContextKeys())); + this._register(this.editorService.onDidVisibleEditorsChange(() => this.updateEditorContextKeys())); + + this._register(this.editorGroupService.onDidAddGroup(() => this.updateEditorContextKeys())); + this._register(this.editorGroupService.onDidRemoveGroup(() => this.updateEditorContextKeys())); + this._register(this.editorGroupService.onDidGroupIndexChange(() => this.updateEditorContextKeys())); + + this._register(addDisposableListener(window, EventType.FOCUS_IN, () => this.updateInputContextKeys(), true)); + + this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateWorkbenchStateContextKey())); + this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.updateWorkspaceFolderCountContextKey())); + + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('workbench.editor.openSideBySideDirection')) { + this.updateSplitEditorsVerticallyContext(); + } + })); + + 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())); + + this._register(this.layoutService.onPartVisibilityChange(() => this.editorAreaVisibleContext.set(this.layoutService.isVisible(Parts.EDITOR_PART)))); } private updateEditorContextKeys(): void { @@ -194,7 +197,7 @@ export class WorkbenchContextKeysHandler extends Disposable { const activeControl = this.editorService.activeControl; const visibleEditors = this.editorService.visibleControls; - this.textCompareEditorActiveContext.set(!!activeControl && activeControl.getId() === TEXT_DIFF_EDITOR_ID); + this.textCompareEditorActiveContext.set(activeControl?.getId() === TEXT_DIFF_EDITOR_ID); this.textCompareEditorVisibleContext.set(visibleEditors.some(control => control.getId() === TEXT_DIFF_EDITOR_ID)); if (visibleEditors.length > 0) { diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 2628b11181..6e88607f3a 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -113,7 +113,7 @@ export function extractResources(e: DragEvent, externalOnly?: boolean): Array r.resource.fsPath === file.path) /* prevent duplicates */) { + if (file?.path /* Electron only */ && !resources.some(r => r.resource.fsPath === file.path) /* prevent duplicates */) { try { resources.push({ resource: URI.file(file.path), isExternal: true }); } catch (error) { @@ -243,11 +243,13 @@ export class ResourcesDropHandler { } // Resolve the contents of the dropped dirty resource from source - try { - const content = await this.backupFileService.resolveBackupContent((droppedDirtyEditor.backupResource!)); - await this.backupFileService.backupResource(droppedDirtyEditor.resource, content.value.create(this.getDefaultEOL()).createSnapshot(true)); - } catch (e) { - // Ignore error + if (droppedDirtyEditor.backupResource) { + try { + const content = await this.backupFileService.resolveBackupContent((droppedDirtyEditor.backupResource)); + await this.backupFileService.backupResource(droppedDirtyEditor.resource, content.value.create(this.getDefaultEOL()).createSnapshot(true)); + } catch (e) { + // Ignore error + } } return false; @@ -358,7 +360,7 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources: for (const textEditorWidget of textEditorWidgets) { if (isCodeEditor(textEditorWidget)) { const model = textEditorWidget.getModel(); - if (model && model.uri && model.uri.toString() === file.resource.toString()) { + if (model?.uri?.toString() === file.resource.toString()) { viewState = textEditorWidget.saveViewState(); break; } diff --git a/src/vs/workbench/browser/editor.ts b/src/vs/workbench/browser/editor.ts index c2e5f3c411..e636641866 100644 --- a/src/vs/workbench/browser/editor.ts +++ b/src/vs/workbench/browser/editor.ts @@ -8,6 +8,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { IConstructorSignature0, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { find } from 'vs/base/common/arrays'; export interface IEditorDescriptor { instantiate(instantiationService: IInstantiationService): BaseEditor; @@ -140,13 +141,7 @@ class EditorRegistry implements IEditorRegistry { } getEditorById(editorId: string): EditorDescriptor | undefined { - for (const editor of this.editors) { - if (editor.getId() === editorId) { - return editor; - } - } - - return undefined; + return find(this.editors, editor => editor.getId() === editorId); } getEditors(): readonly EditorDescriptor[] { diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 85f0a165fb..0e186d8746 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -211,7 +211,7 @@ export class ResourceLabel extends ResourceLabels { constructor( container: HTMLElement, - options: IIconLabelCreationOptions, + options: IIconLabelCreationOptions | undefined, @IInstantiationService instantiationService: IInstantiationService, @IExtensionService extensionService: IExtensionService, @IConfigurationService configurationService: IConfigurationService, @@ -252,7 +252,7 @@ class ResourceLabelWidget extends IconLabel { constructor( container: HTMLElement, - options: IIconLabelCreationOptions, + options: IIconLabelCreationOptions | undefined, @IModeService private readonly modeService: IModeService, @IModelService private readonly modelService: IModelService, @IDecorationsService private readonly decorationsService: IDecorationsService, @@ -434,7 +434,7 @@ class ResourceLabelWidget extends IconLabel { return; } - const iconLabelOptions: IIconLabelValueOptions = { + const iconLabelOptions: IIconLabelValueOptions & { extraClasses: string[] } = { title: '', italic: this.options && this.options.italic, matches: this.options && this.options.matches, @@ -462,7 +462,7 @@ class ResourceLabelWidget extends IconLabel { } if (this.options && this.options.extraClasses) { - iconLabelOptions.extraClasses!.push(...this.options.extraClasses); + iconLabelOptions.extraClasses.push(...this.options.extraClasses); } if (this.options && this.options.fileDecorations && resource) { @@ -477,11 +477,11 @@ class ResourceLabelWidget extends IconLabel { } if (this.options.fileDecorations.colors) { - iconLabelOptions.extraClasses!.push(deco.labelClassName); + iconLabelOptions.extraClasses.push(deco.labelClassName); } if (this.options.fileDecorations.badges) { - iconLabelOptions.extraClasses!.push(deco.badgeClassName); + iconLabelOptions.extraClasses.push(deco.badgeClassName); } } } diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 5c01e2874e..7bc5480bbe 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -21,9 +21,9 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { ITitleService } from 'vs/workbench/services/title/common/titleService'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { LifecyclePhase, StartupKind, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows'; +import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility } from 'vs/platform/windows/common/windows'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; @@ -36,9 +36,10 @@ import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/a import { IFileService } from 'vs/platform/files/common/files'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { coalesce } from 'vs/base/common/arrays'; +import { assertIsDefined } from 'vs/base/common/types'; +import { INotificationService, NotificationsFilter } from 'vs/platform/notification/common/notification'; enum Settings { - MENUBAR_VISIBLE = 'window.menuBarVisibility', ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible', STATUSBAR_VISIBLE = 'workbench.statusBar.visible', @@ -46,7 +47,6 @@ enum Settings { PANEL_POSITION = 'workbench.panel.defaultLocation', ZEN_MODE_RESTORE = 'zenMode.restore', - } enum Storage { @@ -83,9 +83,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi //#region Events - private readonly _onTitleBarVisibilityChange: Emitter = this._register(new Emitter()); - readonly onTitleBarVisibilityChange: Event = this._onTitleBarVisibilityChange.event; - private readonly _onZenModeChange: Emitter = this._register(new Emitter()); readonly onZenModeChange: Event = this._onZenModeChange.event; @@ -98,12 +95,15 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private readonly _onPanelPositionChange: Emitter = this._register(new Emitter()); readonly onPanelPositionChange: Event = this._onPanelPositionChange.event; + private readonly _onPartVisibilityChange: Emitter = this._register(new Emitter()); + readonly onPartVisibilityChange: Event = this._onPartVisibilityChange.event; + private readonly _onLayout = this._register(new Emitter()); readonly onLayout: Event = this._onLayout.event; //#endregion - private _dimension: IDimension; + private _dimension!: IDimension; get dimension(): IDimension { return this._dimension; } private _container: HTMLElement = document.createElement('div'); @@ -111,29 +111,30 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private parts: Map = new Map(); - private workbenchGrid: SerializableGrid; + private workbenchGrid!: SerializableGrid; - private disposed: boolean; + private disposed: boolean | undefined; - private titleBarPartView: ISerializableView; - private activityBarPartView: ISerializableView; - private sideBarPartView: ISerializableView; - private panelPartView: ISerializableView; - private editorPartView: ISerializableView; - private statusBarPartView: ISerializableView; + private titleBarPartView!: ISerializableView; + private activityBarPartView!: ISerializableView; + private sideBarPartView!: ISerializableView; + private panelPartView!: ISerializableView; + private editorPartView!: ISerializableView; + private statusBarPartView!: ISerializableView; - private environmentService: IWorkbenchEnvironmentService; - private configurationService: IConfigurationService; - private lifecycleService: ILifecycleService; - private storageService: IStorageService; - private hostService: IHostService; - private editorService: IEditorService; - private editorGroupService: IEditorGroupsService; - private panelService: IPanelService; - private titleService: ITitleService; - private viewletService: IViewletService; - private contextService: IWorkspaceContextService; - private backupFileService: IBackupFileService; + private environmentService!: IWorkbenchEnvironmentService; + private configurationService!: IConfigurationService; + private lifecycleService!: ILifecycleService; + private storageService!: IStorageService; + private hostService!: IHostService; + private editorService!: IEditorService; + private editorGroupService!: IEditorGroupsService; + private panelService!: IPanelService; + private titleService!: ITitleService; + private viewletService!: IViewletService; + private contextService!: IWorkspaceContextService; + private backupFileService!: IBackupFileService; + private notificationService!: INotificationService; protected readonly state = { fullscreen: false, @@ -181,7 +182,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi transitionedToCenteredEditorLayout: false, wasSideBarVisible: false, wasPanelVisible: false, - transitionDisposables: new DisposableStore() + transitionDisposables: new DisposableStore(), + setNotificationsFilter: false }, }; @@ -209,6 +211,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.panelService = accessor.get(IPanelService); this.viewletService = accessor.get(IViewletService); this.titleService = accessor.get(ITitleService); + this.notificationService = accessor.get(INotificationService); accessor.get(IStatusbarService); // not used, but called to ensure instantiated accessor.get(IActivityBarService); // not used, but called to ensure instantiated @@ -261,8 +264,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.state.menuBar.toggled = visible; if (this.state.fullscreen && (this.state.menuBar.visibility === 'toggle' || this.state.menuBar.visibility === 'default')) { - this._onTitleBarVisibilityChange.fire(); - // Propagate to grid this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); @@ -287,8 +288,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Changing fullscreen state of the window has an impact on custom title bar visibility, so we need to update if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { - this._onTitleBarVisibilityChange.fire(); - // Propagate to grid this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); @@ -326,7 +325,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // Menubar visibility - const newMenubarVisibility = this.configurationService.getValue(Settings.MENUBAR_VISIBLE); + const newMenubarVisibility = getMenuBarVisibility(this.configurationService, this.environmentService); this.setMenubarVisibility(newMenubarVisibility, !!skipLayout); } @@ -340,10 +339,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.state.sideBar.position = position; // Adjust CSS - removeClass(activityBar.getContainer(), oldPositionValue); - removeClass(sideBar.getContainer(), oldPositionValue); - addClass(activityBar.getContainer(), newPositionValue); - addClass(sideBar.getContainer(), newPositionValue); + const activityBarContainer = assertIsDefined(activityBar.getContainer()); + const sideBarContainer = assertIsDefined(sideBar.getContainer()); + removeClass(activityBarContainer, oldPositionValue); + removeClass(sideBarContainer, oldPositionValue); + addClass(activityBarContainer, newPositionValue); + addClass(sideBarContainer, newPositionValue); // Update Styles activityBar.updateStyles(); @@ -371,7 +372,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.state.fullscreen = isFullscreen(); // Menubar visibility - this.state.menuBar.visibility = this.configurationService.getValue(Settings.MENUBAR_VISIBLE); + this.state.menuBar.visibility = getMenuBarVisibility(this.configurationService, this.environmentService); // Activity bar visibility this.state.activityBar.hidden = !this.configurationService.getValue(Settings.ACTIVITYBAR_VISIBLE); @@ -455,7 +456,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Files to diff is exclusive return pathsToEditors(configuration.filesToDiff, fileService).then(filesToDiff => { - if (filesToDiff && filesToDiff.length === 2) { + if (filesToDiff?.length === 2) { return [{ leftResource: filesToDiff[0].resource, rightResource: filesToDiff[1].resource, @@ -528,10 +529,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const container = this.getContainer(part); - return isAncestor(activeElement, container); + return !!container && isAncestor(activeElement, container); } - getContainer(part: Parts): HTMLElement { + getContainer(part: Parts): HTMLElement | undefined { switch (part) { case Parts.TITLEBAR_PART: return this.getPart(Parts.TITLEBAR_PART).getContainer(); @@ -579,7 +580,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return true; // any other part cannot be hidden } - getDimension(part: Parts): Dimension { + getDimension(part: Parts): Dimension | undefined { return this.getPart(part).dimension; } @@ -647,6 +648,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi hideActivityBar: boolean; hideStatusBar: boolean; hideLineNumbers: boolean; + silentNotifications: boolean; } = this.configurationService.getValue('zenMode'); toggleFullScreen = !this.state.fullscreen && config.fullScreen; @@ -676,6 +678,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.state.zenMode.transitionDisposables.add(this.editorGroupService.enforcePartOptions({ showTabs: false })); } + this.state.zenMode.setNotificationsFilter = config.silentNotifications; + if (config.silentNotifications) { + this.notificationService.setFilter(NotificationsFilter.ERROR); + } + if (config.centerLayout) { this.centerEditorLayout(true, true); } @@ -701,6 +708,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.doUpdateLayoutConfiguration(true); this.editorGroupService.activeGroup.focus(); + if (this.state.zenMode.setNotificationsFilter) { + this.notificationService.setFilter(NotificationsFilter.OFF); + } toggleFullScreen = this.state.zenMode.transitionedToFullScreen && this.state.fullscreen; } @@ -747,7 +757,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.workbenchGrid.setViewVisible(this.statusBarPartView, !hidden); } - protected createWorkbenchLayout(instantiationService: IInstantiationService): void { + protected createWorkbenchLayout(): void { const titleBar = this.getPart(Parts.TITLEBAR_PART); const editorPart = this.getPart(Parts.EDITOR_PART); const activityBar = this.getPart(Parts.ACTIVITYBAR_PART); @@ -782,17 +792,18 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.container.prepend(workbenchGrid.element); this.workbenchGrid = workbenchGrid; - this._register((this.sideBarPartView as SidebarPart).onDidVisibilityChange((visible) => { - this.setSideBarHidden(!visible, true); - })); - - this._register((this.panelPartView as PanelPart).onDidVisibilityChange((visible) => { - this.setPanelHidden(!visible, true); - })); - - this._register((this.editorPartView as PanelPart).onDidVisibilityChange((visible) => { - this.setEditorHidden(!visible, true); - })); + [titleBar, editorPart, activityBar, panelPart, sideBar, statusBar].forEach((part: Part) => { + this._register(part.onDidVisibilityChange((visible) => { + this._onPartVisibilityChange.fire(); + if (part === sideBar) { + this.setSideBarHidden(!visible, true); + } else if (part === panelPart) { + this.setPanelHidden(!visible, true); + } else if (part === editorPart) { + this.setEditorHidden(!visible, true); + } + })); + }); this._register(this.storageService.onWillSaveState(() => { const grid = this.workbenchGrid as SerializableGrid; @@ -1121,8 +1132,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.storageService.store(Storage.PANEL_POSITION, positionToString(this.state.panel.position), StorageScope.WORKSPACE); // Adjust CSS - removeClass(panelPart.getContainer(), oldPositionValue); - addClass(panelPart.getContainer(), newPositionValue); + const panelContainer = assertIsDefined(panelPart.getContainer()); + removeClass(panelContainer, oldPositionValue); + addClass(panelContainer, newPositionValue); // Update Styles panelPart.updateStyles(); @@ -1161,7 +1173,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const width = this.storageService.getNumber(Storage.GRID_WIDTH, StorageScope.GLOBAL, workbenchDimensions.width); const height = this.storageService.getNumber(Storage.GRID_HEIGHT, StorageScope.GLOBAL, workbenchDimensions.height); // At some point, we will not fall back to old keys from legacy layout, but for now, let's migrate the keys - const sideBarSize = this.storageService.getNumber(Storage.SIDEBAR_SIZE, StorageScope.GLOBAL, this.storageService.getNumber('workbench.sidebar.width', StorageScope.GLOBAL, Math.min(workbenchDimensions.width / 4, 300))!); + const sideBarSize = this.storageService.getNumber(Storage.SIDEBAR_SIZE, StorageScope.GLOBAL, this.storageService.getNumber('workbench.sidebar.width', StorageScope.GLOBAL, Math.min(workbenchDimensions.width / 4, 300))); const panelSize = this.storageService.getNumber(Storage.PANEL_SIZE, StorageScope.GLOBAL, this.storageService.getNumber(this.state.panel.position === Position.BOTTOM ? 'workbench.panel.height' : 'workbench.panel.width', StorageScope.GLOBAL, workbenchDimensions.height / 3)); const titleBarHeight = this.titleBarPartView.minimumHeight; diff --git a/src/vs/workbench/browser/panel.ts b/src/vs/workbench/browser/panel.ts index 0a8e59e957..5a21c9ade6 100644 --- a/src/vs/workbench/browser/panel.ts +++ b/src/vs/workbench/browser/panel.ts @@ -11,6 +11,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation'; import { isAncestor } from 'vs/base/browser/dom'; +import { assertIsDefined } from 'vs/base/common/types'; export abstract class Panel extends Composite implements IPanel { } @@ -25,7 +26,7 @@ export class PanelDescriptor extends CompositeDescriptor { } export class PanelRegistry extends CompositeRegistry { - private defaultPanelId!: string; + private defaultPanelId: string | undefined; /** * Registers a panel to the platform. @@ -44,7 +45,7 @@ export class PanelRegistry extends CompositeRegistry { /** * Returns a panel by id. */ - getPanel(id: string): PanelDescriptor | null { + getPanel(id: string): PanelDescriptor | undefined { return this.getComposite(id); } @@ -66,7 +67,7 @@ export class PanelRegistry extends CompositeRegistry { * Gets the id of the panel that should open on startup by default. */ getDefaultPanelId(): string { - return this.defaultPanelId; + return assertIsDefined(this.defaultPanelId); } /** @@ -106,13 +107,14 @@ export abstract class TogglePanelAction extends Action { private isPanelActive(): boolean { const activePanel = this.panelService.getActivePanel(); - return !!activePanel && activePanel.getId() === this.panelId; + return activePanel?.getId() === this.panelId; } private isPanelFocused(): boolean { const activeElement = document.activeElement; + const panelPart = this.layoutService.getContainer(Parts.PANEL_PART); - return !!(this.isPanelActive() && activeElement && isAncestor(activeElement, this.layoutService.getContainer(Parts.PANEL_PART))); + return !!(this.isPanelActive() && activeElement && panelPart && isAncestor(activeElement, panelPart)); } } diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index fc7c9078fa..420c082694 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -12,6 +12,7 @@ import { IDimension } from 'vs/platform/layout/browser/layoutService'; import { ISerializableView, IViewSize } from 'vs/base/browser/ui/grid/grid'; import { Event, Emitter } from 'vs/base/common/event'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { assertIsDefined } from 'vs/base/common/types'; export interface IPartOptions { hasTitle?: boolean; @@ -29,13 +30,16 @@ export interface ILayoutContentResult { */ export abstract class Part extends Component implements ISerializableView { - private _dimension: Dimension; - get dimension(): Dimension { return this._dimension; } + private _dimension: Dimension | undefined; + get dimension(): Dimension | undefined { return this._dimension; } - private parent: HTMLElement; - private titleArea: HTMLElement | null = null; - private contentArea: HTMLElement | null = null; - private partLayout: PartLayout; + protected _onDidVisibilityChange = this._register(new Emitter()); + readonly onDidVisibilityChange: Event = this._onDidVisibilityChange.event; + + private parent: HTMLElement | undefined; + private titleArea: HTMLElement | undefined; + private contentArea: HTMLElement | undefined; + private partLayout: PartLayout | undefined; constructor( id: string, @@ -80,35 +84,35 @@ export abstract class Part extends Component implements ISerializableView { /** * Returns the overall part container. */ - getContainer(): HTMLElement { + getContainer(): HTMLElement | undefined { return this.parent; } /** * Subclasses override to provide a title area implementation. */ - protected createTitleArea(parent: HTMLElement, options?: object): HTMLElement | null { - return null; + protected createTitleArea(parent: HTMLElement, options?: object): HTMLElement | undefined { + return undefined; } /** * Returns the title area container. */ - protected getTitleArea(): HTMLElement | null { + protected getTitleArea(): HTMLElement | undefined { return this.titleArea; } /** * Subclasses override to provide a content area implementation. */ - protected createContentArea(parent: HTMLElement, options?: object): HTMLElement | null { - return null; + protected createContentArea(parent: HTMLElement, options?: object): HTMLElement | undefined { + return undefined; } /** * Returns the content area container. */ - protected getContentArea(): HTMLElement | null { + protected getContentArea(): HTMLElement | undefined { return this.contentArea; } @@ -116,7 +120,9 @@ export abstract class Part extends Component implements ISerializableView { * Layout title and content area in the given dimension. */ protected layoutContents(width: number, height: number): ILayoutContentResult { - return this.partLayout.layout(width, height); + const partLayout = assertIsDefined(this.partLayout); + + return partLayout.layout(width, height); } //#region ISerializableView @@ -124,7 +130,7 @@ export abstract class Part extends Component implements ISerializableView { private _onDidChange = this._register(new Emitter()); get onDidChange(): Event { return this._onDidChange.event; } - element: HTMLElement; + element!: HTMLElement; abstract minimumWidth: number; abstract maximumWidth: number; @@ -135,6 +141,10 @@ export abstract class Part extends Component implements ISerializableView { this._dimension = new Dimension(width, height); } + setVisible(visible: boolean) { + this._onDidVisibilityChange.fire(visible); + } + abstract toJSON(): object; //#endregion @@ -144,7 +154,7 @@ class PartLayout { private static readonly TITLE_HEIGHT = 35; - constructor(private options: IPartOptions, private contentArea: HTMLElement | null) { } + constructor(private options: IPartOptions, private contentArea: HTMLElement | undefined) { } layout(width: number, height: number): ILayoutContentResult { diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index 5a6e98b033..a5b34d7d78 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -22,7 +22,7 @@ import { ActivityAction, ActivityActionViewItem, ICompositeBar, ICompositeBarCol import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { IActivity } from 'vs/workbench/common/activity'; -import { ACTIVITY_BAR_FOREGROUND } from 'vs/workbench/common/theme'; +import { ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -60,7 +60,7 @@ export class ViewletActivityAction extends ActivityAction { const activeViewlet = this.viewletService.getActiveViewlet(); // Hide sidebar if selected viewlet already visible - if (sideBarVisible && activeViewlet && activeViewlet.getId() === this.activity.id) { + if (sideBarVisible && activeViewlet?.getId() === this.activity.id) { this.logAction('hide'); this.layoutService.setSideBarHidden(true); return true; @@ -95,7 +95,7 @@ export class ToggleViewletAction extends Action { const activeViewlet = this.viewletService.getActiveViewlet(); // Hide sidebar if selected viewlet already visible - if (sideBarVisible && activeViewlet && activeViewlet.getId() === this._viewlet.id) { + if (sideBarVisible && activeViewlet?.getId() === this._viewlet.id) { this.layoutService.setSideBarHidden(true); return Promise.resolve(); } @@ -163,15 +163,19 @@ export class GlobalActivityActionViewItem extends ActivityActionViewItem { export class PlaceHolderViewletActivityAction extends ViewletActivityAction { constructor( - id: string, name: string, iconUrl: URI, + id: string, + name: string, + iconUrl: URI | undefined, @IViewletService viewletService: IViewletService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService ) { super({ id, name: id, cssClass: `extensionViewlet-placeholder-${id.replace(/\./g, '-')}` }, viewletService, layoutService, telemetryService); - const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${this.class}`; // Generate Placeholder CSS to show the icon in the activity bar - DOM.createCSSRule(iconClass, `-webkit-mask: ${DOM.asCSSUrl(iconUrl)} no-repeat 50% 50%; -webkit-mask-size: 24px;`); + if (iconUrl) { + const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${this.class}`; // Generate Placeholder CSS to show the icon in the activity bar + DOM.createCSSRule(iconClass, `-webkit-mask: ${DOM.asCSSUrl(iconUrl)} no-repeat 50% 50%; -webkit-mask-size: 24px;`); + } } setActivity(activity: IActivity): void { @@ -222,7 +226,7 @@ class SwitchSideBarViewAction extends Action { export class PreviousSideBarViewAction extends SwitchSideBarViewAction { static readonly ID = 'workbench.action.previousSideBarView'; - static LABEL = nls.localize('previousSideBarView', 'Previous Side Bar View'); + static readonly LABEL = nls.localize('previousSideBarView', 'Previous Side Bar View'); constructor( id: string, @@ -241,7 +245,7 @@ export class PreviousSideBarViewAction extends SwitchSideBarViewAction { export class NextSideBarViewAction extends SwitchSideBarViewAction { static readonly ID = 'workbench.action.nextSideBarView'; - static LABEL = nls.localize('nextSideBarView', 'Next Side Bar View'); + static readonly LABEL = nls.localize('nextSideBarView', 'Next Side Bar View'); constructor( id: string, @@ -274,6 +278,25 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } + const activeBorderColor = theme.getColor(ACTIVITY_BAR_ACTIVE_BORDER); + if (activeBorderColor) { + collector.addRule(` + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before { + border-left-color: ${activeBorderColor}; + } + `); + } + + const activeBackgroundColor = theme.getColor(ACTIVITY_BAR_ACTIVE_BACKGROUND); + if (activeBackgroundColor) { + collector.addRule(` + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator { + z-index: 0; + background-color: ${activeBackgroundColor}; + } + `); + } + // Styling with Outline color (e.g. high contrast theme) const outline = theme.getColor(activeContrastBorder); if (outline) { diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index eac46aa53c..285ea4f192 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -5,7 +5,6 @@ import 'vs/css!./media/activitybarpart'; import * as nls from 'vs/nls'; -import { illegalArgument } from 'vs/base/common/errors'; import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -15,10 +14,10 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { IWorkbenchLayoutService, Parts, Position as SideBarPosition } from 'vs/workbench/services/layout/browser/layoutService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { ToggleActivityBarVisibilityAction } from 'vs/workbench/browser/actions/layoutActions'; +import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; +import { ToggleActivityBarVisibilityAction, ToggleMenuBarAction } from 'vs/workbench/browser/actions/layoutActions'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; -import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND } from 'vs/workbench/common/theme'; +import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { CompositeBar, ICompositeBarItem } from 'vs/workbench/browser/parts/compositeBar'; import { Dimension, addClass, removeNode } from 'vs/base/browser/dom'; @@ -30,14 +29,16 @@ import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { IViewsService, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewDescriptorCollection } from 'vs/workbench/common/views'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IViewlet } from 'vs/workbench/common/viewlet'; -import { isUndefinedOrNull } from 'vs/base/common/types'; +import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types'; import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Schemas } from 'vs/base/common/network'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { MenuBarVisibility } from 'vs/platform/windows/common/windows'; +import { getMenuBarVisibility } from 'vs/platform/windows/common/windows'; +import { isWeb } from 'vs/base/common/platform'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; interface ICachedViewlet { id: string; @@ -65,17 +66,17 @@ export class ActivitybarPart extends Part implements IActivityBarService { //#endregion - private globalActivityAction: ActivityAction; - private globalActivityActionBar: ActionBar; + private globalActivityAction: ActivityAction | undefined; + private globalActivityActionBar: ActionBar | undefined; private customMenubar: CustomMenubarControl | undefined; private menubar: HTMLElement | undefined; - private content: HTMLElement; + private content: HTMLElement | undefined; private cachedViewlets: ICachedViewlet[] = []; private compositeBar: CompositeBar; - private compositeActions: Map = new Map(); + private readonly compositeActions: Map = new Map(); private readonly viewletDisposables: Map = new Map(); @@ -89,7 +90,8 @@ export class ActivitybarPart extends Part implements IActivityBarService { @IViewsService private readonly viewsService: IViewsService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService, + @IEnvironmentService private readonly environmentService: IEnvironmentService ) { super(Parts.ACTIVITYBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); @@ -110,8 +112,18 @@ export class ActivitybarPart extends Part implements IActivityBarService { openComposite: (compositeId: string) => this.viewletService.openViewlet(compositeId, true), getActivityAction: (compositeId: string) => this.getCompositeActions(compositeId).activityAction, getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction, - getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(ToggleViewletAction, this.viewletService.getViewlet(compositeId)), - getContextMenuActions: () => [this.instantiationService.createInstance(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, nls.localize('hideActivitBar', "Hide Activity Bar"))], + getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(ToggleViewletAction, assertIsDefined(this.viewletService.getViewlet(compositeId))), + getContextMenuActions: () => { + const menuBarVisibility = getMenuBarVisibility(this.configurationService, this.environmentService); + const actions = []; + + if (menuBarVisibility === 'compact' || (menuBarVisibility === 'hidden' && isWeb)) { + actions.push(this.instantiationService.createInstance(ToggleMenuBarAction, ToggleMenuBarAction.ID, menuBarVisibility === 'compact' ? nls.localize('hideMenu', "Hide Menu") : nls.localize('showMenu', "Show Menu"))); + } + + actions.push(this.instantiationService.createInstance(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, nls.localize('hideActivitBar', "Hide Activity Bar"))); + return actions; + }, getDefaultCompositeId: () => this.viewletService.getDefaultViewletId(), hidePart: () => this.layoutService.setSideBarHidden(true), compositeSize: 50, @@ -147,7 +159,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { // Register for configuration changes this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('window.menuBarVisibility')) { - if (this.configurationService.getValue('window.menuBarVisibility') === 'compact') { + if (getMenuBarVisibility(this.configurationService, this.environmentService) === 'compact') { this.installMenubar(); } else { this.uninstallMenubar(); @@ -169,13 +181,15 @@ export class ActivitybarPart extends Part implements IActivityBarService { if (foundViewlet) { this.compositeBar.addComposite(foundViewlet); } + this.compositeBar.activateComposite(viewlet.getId()); + const viewletDescriptor = this.viewletService.getViewlet(viewlet.getId()); if (viewletDescriptor) { const viewContainer = this.getViewContainer(viewletDescriptor.id); - if (viewContainer && viewContainer.hideIfEmpty) { + if (viewContainer?.hideIfEmpty) { const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); - if (viewDescriptors && viewDescriptors.activeViewDescriptors.length === 0) { + if (viewDescriptors?.activeViewDescriptors.length === 0) { this.hideComposite(viewletDescriptor.id); // Update the composite bar by hiding } } @@ -191,13 +205,15 @@ export class ActivitybarPart extends Part implements IActivityBarService { return this.showGlobalActivity(badge, clazz); } - throw illegalArgument('globalActivityId'); + return Disposable.None; } private showGlobalActivity(badge: IBadge, clazz?: string): IDisposable { - this.globalActivityAction.setBadge(badge, clazz); + const globalActivityAction = assertIsDefined(this.globalActivityAction); - return toDisposable(() => this.globalActivityAction.setBadge(undefined)); + globalActivityAction.setBadge(badge, clazz); + + return toDisposable(() => globalActivityAction.setBadge(undefined)); } private uninstallMenubar() { @@ -213,12 +229,13 @@ export class ActivitybarPart extends Part implements IActivityBarService { private installMenubar() { this.menubar = document.createElement('div'); addClass(this.menubar, 'menubar'); - this.content.prepend(this.menubar); + + const content = assertIsDefined(this.content); + content.prepend(this.menubar); // Menubar: install a custom menu bar depending on configuration this.customMenubar = this._register(this.instantiationService.createInstance(CustomMenubarControl)); this.customMenubar.create(this.menubar); - } createContentArea(parent: HTMLElement): HTMLElement { @@ -229,7 +246,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { parent.appendChild(this.content); // Install menubar if compact - if (this.configurationService.getValue('window.menuBarVisibility') === 'compact') { + if (getMenuBarVisibility(this.configurationService, this.environmentService) === 'compact') { this.installMenubar(); } @@ -243,7 +260,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.createGlobalActivityActionBar(globalActivities); - this.element.style.display = this.layoutService.isVisible(Parts.ACTIVITYBAR_PART) ? null : 'none'; + this.element.style.display = this.layoutService.isVisible(Parts.ACTIVITYBAR_PART) ? '' : 'none'; return this.content; } @@ -252,25 +269,27 @@ export class ActivitybarPart extends Part implements IActivityBarService { super.updateStyles(); // Part container - const container = this.getContainer(); - const background = this.getColor(ACTIVITY_BAR_BACKGROUND); + const container = assertIsDefined(this.getContainer()); + const background = this.getColor(ACTIVITY_BAR_BACKGROUND) || ''; container.style.backgroundColor = background; - const borderColor = this.getColor(ACTIVITY_BAR_BORDER) || this.getColor(contrastBorder); + const borderColor = this.getColor(ACTIVITY_BAR_BORDER) || this.getColor(contrastBorder) || ''; const isPositionLeft = this.layoutService.getSideBarPosition() === SideBarPosition.LEFT; 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; - container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : null; - container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : null; - container.style.borderLeftColor = !isPositionLeft ? borderColor : null; + container.style.borderRightWidth = borderColor && isPositionLeft ? '1px' : ''; + container.style.borderRightStyle = borderColor && isPositionLeft ? 'solid' : ''; + container.style.borderRightColor = isPositionLeft ? borderColor : ''; + container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : ''; + container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : ''; + container.style.borderLeftColor = !isPositionLeft ? borderColor : ''; } private getActivitybarItemColors(theme: ITheme): ICompositeBarColors { return { activeForegroundColor: theme.getColor(ACTIVITY_BAR_FOREGROUND), inactiveForegroundColor: theme.getColor(ACTIVITY_BAR_INACTIVE_FOREGROUND), + activeBorderColor: theme.getColor(ACTIVITY_BAR_ACTIVE_BORDER), + activeBackground: theme.getColor(ACTIVITY_BAR_ACTIVE_BACKGROUND), badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND), badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND), dragAndDropBackground: theme.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND), @@ -280,7 +299,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { private createGlobalActivityActionBar(container: HTMLElement): void { this.globalActivityActionBar = this._register(new ActionBar(container, { - actionViewItemProvider: a => this.instantiationService.createInstance(GlobalActivityActionViewItem, a, (theme: ITheme) => this.getActivitybarItemColors(theme)), + actionViewItemProvider: action => this.instantiationService.createInstance(GlobalActivityActionViewItem, action as ActivityAction, (theme: ITheme) => this.getActivitybarItemColors(theme)), orientation: ActionsOrientation.VERTICAL, ariaLabel: nls.localize('manage', "Manage"), animated: false @@ -291,6 +310,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { name: nls.localize('manage', "Manage"), cssClass: 'update-activity' }); + this.globalActivityActionBar.push(this.globalActivityAction); } @@ -306,7 +326,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { } else { const cachedComposite = this.cachedViewlets.filter(c => c.id === compositeId)[0]; compositeActions = { - activityAction: this.instantiationService.createInstance(PlaceHolderViewletActivityAction, compositeId, cachedComposite && cachedComposite.name ? cachedComposite.name : compositeId, cachedComposite && cachedComposite.iconUrl ? URI.revive(cachedComposite.iconUrl) : undefined), + activityAction: this.instantiationService.createInstance(PlaceHolderViewletActivityAction, compositeId, cachedComposite?.name || compositeId, cachedComposite?.iconUrl ? URI.revive(cachedComposite.iconUrl) : undefined), pinnedAction: new PlaceHolderToggleCompositePinnedAction(compositeId, this.compositeBar) }; } @@ -321,7 +341,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { for (const viewlet of viewlets) { const cachedViewlet = this.cachedViewlets.filter(({ id }) => id === viewlet.id)[0]; const activeViewlet = this.viewletService.getActiveViewlet(); - const isActive = activeViewlet && activeViewlet.getId() === viewlet.id; + const isActive = activeViewlet?.getId() === viewlet.id; if (isActive || !this.shouldBeHidden(viewlet.id, cachedViewlet)) { this.compositeBar.addComposite(viewlet); @@ -336,10 +356,11 @@ export class ActivitybarPart extends Part implements IActivityBarService { } } } + for (const viewlet of viewlets) { this.enableCompositeActions(viewlet); const viewContainer = this.getViewContainer(viewlet.id); - if (viewContainer && viewContainer.hideIfEmpty) { + if (viewContainer?.hideIfEmpty) { const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); if (viewDescriptors) { this.onDidChangeActiveViews(viewlet, viewDescriptors); @@ -354,6 +375,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { if (disposable) { disposable.dispose(); } + this.viewletDisposables.delete(viewletId); this.hideComposite(viewletId); } @@ -371,7 +393,8 @@ export class ActivitybarPart extends Part implements IActivityBarService { if (!viewContainer || !viewContainer.hideIfEmpty) { return false; } - return cachedViewlet && cachedViewlet.views && cachedViewlet.views.length + + return cachedViewlet?.views && cachedViewlet.views.length ? cachedViewlet.views.every(({ when }) => !!when && !this.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(when))) : viewletId === TEST_VIEW_CONTAINER_ID /* Hide Test viewlet for the first time or it had no views registered before */; } @@ -387,6 +410,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { private hideComposite(compositeId: string): void { this.compositeBar.hideComposite(compositeId); + const compositeActions = this.compositeActions.get(compositeId); if (compositeActions) { compositeActions.activityAction.dispose(); @@ -415,12 +439,6 @@ export class ActivitybarPart extends Part implements IActivityBarService { .map(v => v.id); } - setVisible(visible: boolean): void { - if (this.element) { - this.element.style.display = visible ? null : 'none'; - } - } - layout(width: number, height: number): void { if (!this.layoutService.isVisible(Parts.ACTIVITYBAR_PART)) { return; @@ -440,7 +458,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { if (e.key === ActivitybarPart.PINNED_VIEWLETS && e.scope === StorageScope.GLOBAL && this.cachedViewletsValue !== this.getStoredCachedViewletsValue() /* This checks if current window changed the value or not */) { - this._cachedViewletsValue = null; + this._cachedViewletsValue = undefined; const newCompositeItems: ICompositeBarItem[] = []; const compositeItems = this.compositeBar.getCompositeBarItems(); const cachedViewlets = this.getCachedViewlets(); @@ -525,7 +543,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { return result; } - private _cachedViewletsValue: string | null; + private _cachedViewletsValue: string | undefined; private get cachedViewletsValue(): string { if (!this._cachedViewletsValue) { this._cachedViewletsValue = this.getStoredCachedViewletsValue(); diff --git a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css index b0ff193562..91c9d27e92 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css @@ -10,6 +10,8 @@ } .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-label { + position: relative; + z-index: 1; display: flex; overflow: hidden; height: 40px; @@ -20,19 +22,41 @@ font-size: 15px; } +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before, .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before { content: ""; position: absolute; top: 9px; height: 32px; + z-index: 1; + top: 5px; + height: 40px; width: 0; border-left: 2px solid; } +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before { + top: 0; + height: 100%; +} + +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:focus .active-item-indicator:before { + border-left: none; /* don't show active border + focus at the same time, focus takes priority */ +} + +/* Hides active elements in high contrast mode */ +.hc-black .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator { + display: none; +} + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.clicked:focus:before { border-left: none !important; /* no focus feedback when using mouse */ } +.monaco-workbench .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before{ + left: 0; +} + .monaco-workbench .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before { left: 1px; } @@ -41,8 +65,20 @@ right: 1px; } +.monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before { + right: 2px; +} + +/* Hides outline on HC as focus is handled by border */ +.hc-black .monaco-workbench .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before, +.hc-black .monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before { + outline: none; +} + +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .active-item-indicator, .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge { position: absolute; + z-index: 1; top: 5px; left: 0; overflow: hidden; diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index ebedec7d5b..e1acaf49ce 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -30,11 +30,12 @@ export interface ICompositeBarItem { } export interface ICompositeBarOptions { - icon: boolean; - orientation: ActionsOrientation; - colors: (theme: ITheme) => ICompositeBarColors; - compositeSize: number; - overflowActionSize: number; + readonly icon: boolean; + readonly orientation: ActionsOrientation; + readonly colors: (theme: ITheme) => ICompositeBarColors; + readonly compositeSize: number; + readonly overflowActionSize: number; + getActivityAction: (compositeId: string) => ActivityAction; getCompositePinnedAction: (compositeId: string) => Action; getOnCompositeClickAction: (compositeId: string) => Action; @@ -48,7 +49,7 @@ export class CompositeBar extends Widget implements ICompositeBar { private dimension: Dimension | undefined; - private compositeSwitcherBar!: ActionBar; + private compositeSwitcherBar: ActionBar | undefined; private compositeOverflowAction: CompositeOverflowActivityAction | undefined; private compositeOverflowActionViewItem: CompositeOverflowActivityActionViewItem | undefined; @@ -92,13 +93,14 @@ export class CompositeBar extends Widget implements ICompositeBar { create(parent: HTMLElement): HTMLElement { const actionBarDiv = parent.appendChild($('.composite-bar')); + this.compositeSwitcherBar = this._register(new ActionBar(actionBarDiv, { actionViewItemProvider: (action: Action) => { if (action instanceof CompositeOverflowActivityAction) { return this.compositeOverflowActionViewItem; } const item = this.model.findItem(action.id); - return item && this.instantiationService.createInstance(CompositeActionViewItem, action, item.pinnedAction, () => this.getContextMenuActions(), this.options.colors, this.options.icon, this); + return item && this.instantiationService.createInstance(CompositeActionViewItem, action as ActivityAction, item.pinnedAction, () => this.getContextMenuActions() as Action[], this.options.colors, this.options.icon, this); }, orientation: this.options.orientation, ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"), @@ -113,12 +115,15 @@ export class CompositeBar extends Widget implements ICompositeBar { if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { EventHelper.stop(e, true); - const draggedCompositeId = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype)![0].id; - this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype); + const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype); + if (Array.isArray(data)) { + const draggedCompositeId = data[0].id; + this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype); - const targetItem = this.model.visibleItems[this.model.visibleItems.length - 1]; - if (targetItem && targetItem.id !== draggedCompositeId) { - this.move(draggedCompositeId, targetItem.id); + const targetItem = this.model.visibleItems[this.model.visibleItems.length - 1]; + if (targetItem && targetItem.id !== draggedCompositeId) { + this.move(draggedCompositeId, targetItem.id); + } } } })); @@ -258,7 +263,7 @@ export class CompositeBar extends Widget implements ICompositeBar { isPinned(compositeId: string): boolean { const item = this.model.findItem(compositeId); - return item && item.pinned; + return item?.pinned; } move(compositeId: string, toCompositeId: string): void { @@ -270,7 +275,7 @@ export class CompositeBar extends Widget implements ICompositeBar { getAction(compositeId: string): ActivityAction { const item = this.model.findItem(compositeId); - return item && item.activityAction; + return item?.activityAction; } private computeSizes(items: ICompositeBarModelItem[]): void { @@ -278,21 +283,23 @@ export class CompositeBar extends Widget implements ICompositeBar { if (size) { items.forEach(composite => this.compositeSizeInBar.set(composite.id, size)); } else { - if (this.dimension && this.dimension.height !== 0 && this.dimension.width !== 0) { + const compositeSwitcherBar = this.compositeSwitcherBar; + if (compositeSwitcherBar && this.dimension && this.dimension.height !== 0 && this.dimension.width !== 0) { // Compute sizes only if visible. Otherwise the size measurment would be computed wrongly. - const currentItemsLength = this.compositeSwitcherBar.viewItems.length; - this.compositeSwitcherBar.push(items.map(composite => composite.activityAction)); + const currentItemsLength = compositeSwitcherBar.viewItems.length; + compositeSwitcherBar.push(items.map(composite => composite.activityAction)); items.map((composite, index) => this.compositeSizeInBar.set(composite.id, this.options.orientation === ActionsOrientation.VERTICAL - ? this.compositeSwitcherBar.getHeight(currentItemsLength + index) - : this.compositeSwitcherBar.getWidth(currentItemsLength + index) + ? compositeSwitcherBar.getHeight(currentItemsLength + index) + : compositeSwitcherBar.getWidth(currentItemsLength + index) )); - items.forEach(() => this.compositeSwitcherBar.pull(this.compositeSwitcherBar.viewItems.length - 1)); + items.forEach(() => compositeSwitcherBar.pull(compositeSwitcherBar.viewItems.length - 1)); } } } private updateCompositeSwitcher(): void { - if (!this.compositeSwitcherBar || !this.dimension) { + const compositeSwitcherBar = this.compositeSwitcherBar; + if (!compositeSwitcherBar || !this.dimension) { return; // We have not been rendered yet so there is nothing to update. } @@ -340,7 +347,7 @@ export class CompositeBar extends Widget implements ICompositeBar { // Pull out overflow action if there is a composite change so that we can add it to the end later if (this.compositeOverflowAction && visibleCompositesChange) { - this.compositeSwitcherBar.pull(this.compositeSwitcherBar.length() - 1); + compositeSwitcherBar.pull(compositeSwitcherBar.length() - 1); this.compositeOverflowAction.dispose(); this.compositeOverflowAction = undefined; @@ -359,8 +366,8 @@ export class CompositeBar extends Widget implements ICompositeBar { } }); compositesToRemove.reverse().forEach(index => { - const actionViewItem = this.compositeSwitcherBar.viewItems[index]; - this.compositeSwitcherBar.pull(index); + const actionViewItem = compositeSwitcherBar.viewItems[index]; + compositeSwitcherBar.pull(index); actionViewItem.dispose(); this.visibleComposites.splice(index, 1); }); @@ -370,19 +377,19 @@ export class CompositeBar extends Widget implements ICompositeBar { const currentIndex = this.visibleComposites.indexOf(compositeId); if (newIndex !== currentIndex) { if (currentIndex !== -1) { - const actionViewItem = this.compositeSwitcherBar.viewItems[currentIndex]; - this.compositeSwitcherBar.pull(currentIndex); + const actionViewItem = compositeSwitcherBar.viewItems[currentIndex]; + compositeSwitcherBar.pull(currentIndex); actionViewItem.dispose(); this.visibleComposites.splice(currentIndex, 1); } - this.compositeSwitcherBar.push(this.model.findItem(compositeId).activityAction, { label: true, icon: this.options.icon, index: newIndex }); + compositeSwitcherBar.push(this.model.findItem(compositeId).activityAction, { label: true, icon: this.options.icon, index: newIndex }); this.visibleComposites.splice(newIndex, 0, compositeId); } }); // Add overflow action as needed - if ((visibleCompositesChange && overflows) || this.compositeSwitcherBar.length() === 0) { + if ((visibleCompositesChange && overflows) || compositeSwitcherBar.length() === 0) { this.compositeOverflowAction = this.instantiationService.createInstance(CompositeOverflowActivityAction, () => { if (this.compositeOverflowActionViewItem) { this.compositeOverflowActionViewItem.showMenu(); @@ -395,13 +402,13 @@ export class CompositeBar extends Widget implements ICompositeBar { () => this.model.activeItem ? this.model.activeItem.id : undefined, (compositeId: string) => { const item = this.model.findItem(compositeId); - return item && item.activity[0] && item.activity[0].badge; + return item?.activity?.[0].badge; }, this.options.getOnCompositeClickAction, this.options.colors ); - this.compositeSwitcherBar.push(this.compositeOverflowAction, { label: false, icon: true }); + compositeSwitcherBar.push(this.compositeOverflowAction, { label: false, icon: true }); } this._onDidChange.fire(); diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index fb96c105eb..fec6fa3bdd 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -57,7 +57,7 @@ export class ActivityAction extends Action { private readonly _onDidChangeBadge = new Emitter(); readonly onDidChangeBadge: Event = this._onDidChangeBadge.event; - private badge?: IBadge; + private badge: IBadge | undefined; private clazz: string | undefined; constructor(private _activity: IActivity) { @@ -110,6 +110,8 @@ export class ActivityAction extends Action { export interface ICompositeBarColors { activeBackgroundColor?: Color; inactiveBackgroundColor?: Color; + activeBorderColor?: Color; + activeBackground?: Color; activeBorderBottomColor?: Color; activeForegroundColor?: Color; inactiveForegroundColor?: Color; @@ -156,12 +158,12 @@ export class ActivityActionViewItem extends BaseActionViewItem { if (this.label) { if (this.options.icon) { const foreground = this._action.checked ? colors.activeBackgroundColor || colors.activeForegroundColor : colors.inactiveBackgroundColor || colors.inactiveForegroundColor; - this.label.style.backgroundColor = foreground ? foreground.toString() : null; + this.label.style.backgroundColor = foreground ? foreground.toString() : ''; } else { const foreground = this._action.checked ? colors.activeForegroundColor : colors.inactiveForegroundColor; const borderBottomColor = this._action.checked ? colors.activeBorderBottomColor : null; this.label.style.color = foreground ? foreground.toString() : null; - this.label.style.borderBottomColor = borderBottomColor ? borderBottomColor.toString() : null; + this.label.style.borderBottomColor = borderBottomColor ? borderBottomColor.toString() : ''; } } @@ -172,11 +174,11 @@ export class ActivityActionViewItem extends BaseActionViewItem { const contrastBorderColor = theme.getColor(contrastBorder); this.badgeContent.style.color = badgeForeground ? badgeForeground.toString() : null; - this.badgeContent.style.backgroundColor = badgeBackground ? badgeBackground.toString() : null; + this.badgeContent.style.backgroundColor = badgeBackground ? badgeBackground.toString() : ''; - this.badgeContent.style.borderStyle = contrastBorderColor ? 'solid' : null; - this.badgeContent.style.borderWidth = contrastBorderColor ? '1px' : null; - this.badgeContent.style.borderColor = contrastBorderColor ? contrastBorderColor.toString() : null; + this.badgeContent.style.borderStyle = contrastBorderColor ? 'solid' : ''; + this.badgeContent.style.borderWidth = contrastBorderColor ? '1px' : ''; + this.badgeContent.style.borderColor = contrastBorderColor ? contrastBorderColor.toString() : ''; } } @@ -205,12 +207,18 @@ export class ActivityActionViewItem extends BaseActionViewItem { })); // Label - this.label = dom.append(this.element!, dom.$('a')); + this.label = dom.append(container, dom.$('a')); // Badge - this.badge = dom.append(this.element!, dom.$('.badge')); + this.badge = dom.append(container, dom.$('.badge')); this.badgeContent = dom.append(this.badge, dom.$('.badge-content')); + // Activity bar active border + background + const isActivityBarItem = this.options.icon; + if (isActivityBarItem) { + dom.append(container, dom.$('.active-item-indicator')); + } + dom.hide(this.badge); this.updateActivity(); @@ -285,7 +293,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { // Title let title: string; - if (badge && badge.getDescription()) { + if (badge?.getDescription()) { if (this.activity.name) { title = nls.localize('badgeTitle', "{0} - {1}", this.activity.name, badge.getDescription()); } else { @@ -294,14 +302,17 @@ export class ActivityActionViewItem extends BaseActionViewItem { } else { title = this.activity.name; } + this.updateTitle(title); } protected updateLabel(): void { this.label.className = 'action-label'; + if (this.activity.cssClass) { dom.addClass(this.label, this.activity.cssClass); } + if (!this.options.icon) { this.label.textContent = this.getAction().label; } @@ -347,12 +358,12 @@ export class CompositeOverflowActivityAction extends ActivityAction { } export class CompositeOverflowActivityActionViewItem extends ActivityActionViewItem { - private actions: Action[] | undefined; + private actions: Action[] = []; constructor( action: ActivityAction, - private getOverflowingComposites: () => { id: string, name: string }[], - private getActiveCompositeId: () => string, + private getOverflowingComposites: () => { id: string, name?: string }[], + private getActiveCompositeId: () => string | undefined, private getBadge: (compositeId: string) => IBadge, private getCompositeOpenAction: (compositeId: string) => Action, colors: (theme: ITheme) => ICompositeBarColors, @@ -370,16 +381,17 @@ export class CompositeOverflowActivityActionViewItem extends ActivityActionViewI this.actions = this.getActions(); this.contextMenuService.showContextMenu({ - getAnchor: () => this.element!, - getActions: () => this.actions!, - onHide: () => dispose(this.actions!) + getAnchor: () => this.container, + getActions: () => this.actions, + getCheckedActionsRepresentation: () => 'radio', + onHide: () => dispose(this.actions) }); } private getActions(): Action[] { return this.getOverflowingComposites().map(composite => { const action = this.getCompositeOpenAction(composite.id); - action.radio = this.getActiveCompositeId() === action.id; + action.checked = this.getActiveCompositeId() === action.id; const badge = this.getBadge(composite.id); let suffix: string | number | undefined; @@ -392,7 +404,7 @@ export class CompositeOverflowActivityActionViewItem extends ActivityActionViewI if (suffix) { action.label = nls.localize('numberBadge', "{0} ({1})", composite.name, suffix); } else { - action.label = composite.name; + action.label = composite.name || ''; } return action; @@ -439,7 +451,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem { constructor( private compositeActivityAction: ActivityAction, private toggleCompositePinnedAction: Action, - private contextMenuActionsProvider: () => Action[], + private contextMenuActionsProvider: () => ReadonlyArray, colors: (theme: ITheme) => ICompositeBarColors, icon: boolean, private compositeBar: ICompositeBar, @@ -502,7 +514,9 @@ export class CompositeActionViewItem extends ActivityActionViewItem { // Allow to drag this._register(dom.addDisposableListener(this.container, dom.EventType.DRAG_START, (e: DragEvent) => { - e.dataTransfer!.effectAllowed = 'move'; + if (e.dataTransfer) { + e.dataTransfer.effectAllowed = 'move'; + } // Registe as dragged to local transfer this.compositeTransfer.setData([new DraggedCompositeIdentifier(this.activity.id)], DraggedCompositeIdentifier.prototype); @@ -515,8 +529,11 @@ export class CompositeActionViewItem extends ActivityActionViewItem { this._register(new DragAndDropObserver(this.container, { onDragEnter: e => { - if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype) && this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype)![0].id !== this.activity.id) { - this.updateFromDragging(container, true); + if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { + const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype); + if (Array.isArray(data) && data[0].id !== this.activity.id) { + this.updateFromDragging(container, true); + } } }, @@ -538,12 +555,15 @@ export class CompositeActionViewItem extends ActivityActionViewItem { dom.EventHelper.stop(e, true); if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { - const draggedCompositeId = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype)![0].id; - if (draggedCompositeId !== this.activity.id) { - this.updateFromDragging(container, false); - this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype); + const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype); + if (Array.isArray(data)) { + const draggedCompositeId = data[0].id; + if (draggedCompositeId !== this.activity.id) { + this.updateFromDragging(container, false); + this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype); - this.compositeBar.move(draggedCompositeId, this.activity.id); + this.compositeBar.move(draggedCompositeId, this.activity.id); + } } } } @@ -563,7 +583,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem { const theme = this.themeService.getTheme(); const dragBackground = this.options.colors(theme).dragAndDropBackground; - element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : null; + element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : ''; } private showContextMenu(container: HTMLElement): void { @@ -595,8 +615,8 @@ export class CompositeActionViewItem extends ActivityActionViewItem { this.contextMenuService.showContextMenu({ getAnchor: () => anchor, - getActionsContext: () => this.activity.id, - getActions: () => actions + getActions: () => actions, + getActionsContext: () => this.activity.id }); } diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 971ea0ea81..3884885d31 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -32,6 +32,7 @@ import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { Dimension, append, $, addClass, hide, show, addClasses } from 'vs/base/browser/dom'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; +import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types'; export interface ICompositeTitleLabel { @@ -57,15 +58,15 @@ export abstract class CompositePart extends Part { protected readonly onDidCompositeOpen = this._register(new Emitter<{ composite: IComposite, focus: boolean }>()); protected readonly onDidCompositeClose = this._register(new Emitter()); - protected toolBar!: ToolBar; + protected toolBar: ToolBar | undefined; private mapCompositeToCompositeContainer = new Map(); private mapActionsBindingToComposite = new Map void>(); - private activeComposite: Composite | null; + private activeComposite: Composite | undefined; private lastActiveCompositeId: string; private instantiatedCompositeItems: Map; - private titleLabel!: ICompositeTitleLabel; - private progressBar!: ProgressBar; + private titleLabel: ICompositeTitleLabel | undefined; + private progressBar: ProgressBar | undefined; private contentAreaSize: Dimension | undefined; private readonly telemetryActionsListener = this._register(new MutableDisposable()); private currentCompositeOpenToken: string | undefined; @@ -90,7 +91,6 @@ export abstract class CompositePart extends Part { ) { super(id, options, themeService, storageService, layoutService); - this.activeComposite = null; this.instantiatedCompositeItems = new Map(); this.lastActiveCompositeId = storageService.get(activeCompositeSettingsKey, StorageScope.WORKSPACE, this.defaultCompositeId); } @@ -168,7 +168,7 @@ export abstract class CompositePart extends Part { // Instantiate composite from registry otherwise const compositeDescriptor = this.registry.getComposite(id); if (compositeDescriptor) { - const compositeProgressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, this.progressBar, compositeDescriptor.id, isActive); + const compositeProgressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, this.progressBar, compositeDescriptor.id, !!isActive); const compositeInstantiationService = this.instantiationService.createChild(new ServiceCollection( [IEditorProgressService, compositeProgressIndicator] // provide the editor progress service for any editors instantiated within the composite )); @@ -234,7 +234,8 @@ export abstract class CompositePart extends Part { show(compositeContainer); // Setup action runner - this.toolBar.actionRunner = composite.getActionRunner(); + const toolBar = assertIsDefined(this.toolBar); + toolBar.actionRunner = composite.getActionRunner(); // Update title with composite title if it differs from descriptor const descriptor = this.registry.getComposite(composite.getId()); @@ -251,7 +252,7 @@ export abstract class CompositePart extends Part { actionsBinding(); // Action Run Handling - this.telemetryActionsListener.value = this.toolBar.actionRunner.onDidRun(e => { + this.telemetryActionsListener.value = toolBar.actionRunner.onDidRun(e => { // Check for Error if (e.error && !errors.isPromiseCanceledError(e.error)) { @@ -310,9 +311,10 @@ export abstract class CompositePart extends Part { const keybinding = this.keybindingService.lookupKeybinding(compositeId); - this.titleLabel.updateTitle(compositeId, compositeTitle, (keybinding && keybinding.getLabel()) || undefined); + this.titleLabel.updateTitle(compositeId, compositeTitle, withNullAsUndefined(keybinding?.getLabel())); - this.toolBar.setAriaLabel(nls.localize('ariaCompositeToolbarLabel', "{0} actions", compositeTitle)); + const toolBar = assertIsDefined(this.toolBar); + toolBar.setAriaLabel(nls.localize('ariaCompositeToolbarLabel', "{0} actions", compositeTitle)); } private collectCompositeActions(composite: Composite): () => void { @@ -326,13 +328,14 @@ export abstract class CompositePart extends Part { secondaryActions.push(...this.getSecondaryActions()); // Update context - this.toolBar.context = this.actionsContextProvider(); + const toolBar = assertIsDefined(this.toolBar); + toolBar.context = this.actionsContextProvider(); // Return fn to set into toolbar - return this.toolBar.setActions(prepareActions(primaryActions), prepareActions(secondaryActions)); + return toolBar.setActions(prepareActions(primaryActions), prepareActions(secondaryActions)); } - protected getActiveComposite(): IComposite | null { + protected getActiveComposite(): IComposite | undefined { return this.activeComposite; } @@ -346,7 +349,7 @@ export abstract class CompositePart extends Part { } const composite = this.activeComposite; - this.activeComposite = null; + this.activeComposite = undefined; const compositeContainer = this.mapCompositeToCompositeContainer.get(composite.getId()); @@ -360,10 +363,14 @@ export abstract class CompositePart extends Part { } // Clear any running Progress - this.progressBar.stop().hide(); + if (this.progressBar) { + this.progressBar.stop().hide(); + } // Empty Actions - this.toolBar.setActions([])(); + if (this.toolBar) { + this.toolBar.setActions([])(); + } this.onDidCompositeClose.fire(composite); return composite; @@ -413,7 +420,8 @@ export abstract class CompositePart extends Part { super.updateStyles(); // Forward to title label - this.titleLabel.updateStyles(); + const titleLabel = assertIsDefined(this.titleLabel); + titleLabel.updateStyles(); } protected actionViewItemProvider(action: IAction): IActionViewItem | undefined { @@ -446,10 +454,10 @@ export abstract class CompositePart extends Part { return contentContainer; } - getProgressIndicator(id: string): IProgressIndicator | null { + getProgressIndicator(id: string): IProgressIndicator | undefined { const compositeItem = this.instantiatedCompositeItems.get(id); - return compositeItem ? compositeItem.progress : null; + return compositeItem ? compositeItem.progress : undefined; } protected getActions(): ReadonlyArray { diff --git a/src/vs/workbench/browser/parts/editor/binaryEditor.ts b/src/vs/workbench/browser/parts/editor/binaryEditor.ts index ef6c37a46d..e983819609 100644 --- a/src/vs/workbench/browser/parts/editor/binaryEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryEditor.ts @@ -19,6 +19,7 @@ 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 { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; export interface IOpenCallbacks { openInternal: (input: EditorInput, options: EditorOptions | undefined) => Promise; @@ -38,8 +39,8 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { private callbacks: IOpenCallbacks; private metadata: string | undefined; - private binaryContainer: HTMLElement; - private scrollbar: DomScrollableElement; + private binaryContainer: HTMLElement | undefined; + private scrollbar: DomScrollableElement | undefined; private resourceViewerContext: ResourceViewerContext | undefined; constructor( @@ -91,7 +92,8 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { this.resourceViewerContext.dispose(); } - this.resourceViewerContext = ResourceViewer.show({ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() }, this.binaryContainer, this.scrollbar, { + const [binaryContainer, scrollbar] = assertAllDefined(this.binaryContainer, this.scrollbar); + this.resourceViewerContext = ResourceViewer.show({ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() }, binaryContainer, scrollbar, { openInternalClb: () => this.handleOpenInternalCallback(input, options), openExternalClb: this.environmentService.configuration.remoteAuthority ? undefined : resource => this.callbacks.openExternal(resource), metadataClb: meta => this.handleMetadataChanged(meta) @@ -120,8 +122,10 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { // Clear Meta this.handleMetadataChanged(undefined); - // Clear Resource Viewer - clearNode(this.binaryContainer); + // Clear the rest + if (this.binaryContainer) { + clearNode(this.binaryContainer); + } dispose(this.resourceViewerContext); this.resourceViewerContext = undefined; @@ -131,19 +135,24 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { layout(dimension: Dimension): void { // Pass on to Binary Container - size(this.binaryContainer, dimension.width, dimension.height); - this.scrollbar.scanDomNode(); + const [binaryContainer, scrollbar] = assertAllDefined(this.binaryContainer, this.scrollbar); + size(binaryContainer, dimension.width, dimension.height); + scrollbar.scanDomNode(); if (this.resourceViewerContext && this.resourceViewerContext.layout) { this.resourceViewerContext.layout(dimension); } } focus(): void { - this.binaryContainer.focus(); + const binaryContainer = assertIsDefined(this.binaryContainer); + + binaryContainer.focus(); } dispose(): void { - this.binaryContainer.remove(); + if (this.binaryContainer) { + this.binaryContainer.remove(); + } dispose(this.resourceViewerContext); this.resourceViewerContext = undefined; diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts index 0aeb3217a3..6b2d52784c 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts @@ -66,14 +66,14 @@ export abstract class BreadcrumbsConfig { // internal } - static IsEnabled = BreadcrumbsConfig._stub('breadcrumbs.enabled'); - static UseQuickPick = BreadcrumbsConfig._stub('breadcrumbs.useQuickPick'); - static FilePath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.filePath'); - static SymbolPath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.symbolPath'); - static SymbolSortOrder = BreadcrumbsConfig._stub<'position' | 'name' | 'type'>('breadcrumbs.symbolSortOrder'); - static Icons = BreadcrumbsConfig._stub('breadcrumbs.icons'); + static readonly IsEnabled = BreadcrumbsConfig._stub('breadcrumbs.enabled'); + static readonly UseQuickPick = BreadcrumbsConfig._stub('breadcrumbs.useQuickPick'); + static readonly FilePath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.filePath'); + static readonly SymbolPath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.symbolPath'); + static readonly SymbolSortOrder = BreadcrumbsConfig._stub<'position' | 'name' | 'type'>('breadcrumbs.symbolSortOrder'); + static readonly Icons = BreadcrumbsConfig._stub('breadcrumbs.icons'); - static FileExcludes = BreadcrumbsConfig._stub('files.exclude'); + static readonly FileExcludes = BreadcrumbsConfig._stub('files.exclude'); private static _stub(name: string): { bindTo(service: IConfigurationService): BreadcrumbsConfig } { return { @@ -166,6 +166,136 @@ Registry.as(Extensions.Configuration).registerConfigurat description: localize('icons', "Render breadcrumb items with icons."), type: 'boolean', default: true + }, + 'breadcrumbs.filteredTypes.file': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.file', "When set to `false` breadcrumbs never show `file`-symbols.") + }, + 'breadcrumbs.filteredTypes.module': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.module', "When set to `false` breadcrumbs never show `module`-symbols.") + }, + 'breadcrumbs.filteredTypes.namespace': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.namespace', "When set to `false` breadcrumbs never show `namespace`-symbols.") + }, + 'breadcrumbs.filteredTypes.package': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.package', "When set to `false` breadcrumbs never show `package`-symbols.") + }, + 'breadcrumbs.filteredTypes.class': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.class', "When set to `false` breadcrumbs never show `class`-symbols.") + }, + 'breadcrumbs.filteredTypes.method': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.method', "When set to `false` breadcrumbs never show `method`-symbols.") + }, + 'breadcrumbs.filteredTypes.property': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.property', "When set to `false` breadcrumbs never show `property`-symbols.") + }, + 'breadcrumbs.filteredTypes.field': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.field', "When set to `false` breadcrumbs never show `field`-symbols.") + }, + 'breadcrumbs.filteredTypes.constructor': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.constructor', "When set to `false` breadcrumbs never show `constructor`-symbols.") + }, + 'breadcrumbs.filteredTypes.enum': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.enum', "When set to `false` breadcrumbs never show `enum`-symbols.") + }, + 'breadcrumbs.filteredTypes.interface': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.interface', "When set to `false` breadcrumbs never show `interface`-symbols.") + }, + 'breadcrumbs.filteredTypes.function': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.function', "When set to `false` breadcrumbs never show `function`-symbols.") + }, + 'breadcrumbs.filteredTypes.variable': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.variable', "When set to `false` breadcrumbs never show `variable`-symbols.") + }, + 'breadcrumbs.filteredTypes.constant': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.constant', "When set to `false` breadcrumbs never show `constant`-symbols.") + }, + 'breadcrumbs.filteredTypes.string': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.string', "When set to `false` breadcrumbs never show `string`-symbols.") + }, + 'breadcrumbs.filteredTypes.number': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.number', "When set to `false` breadcrumbs never show `number`-symbols.") + }, + 'breadcrumbs.filteredTypes.boolean': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.boolean', "When set to `false` breadcrumbs never show `boolean`-symbols.") + }, + 'breadcrumbs.filteredTypes.array': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.array', "When set to `false` breadcrumbs never show `array`-symbols.") + }, + 'breadcrumbs.filteredTypes.object': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.object', "When set to `false` breadcrumbs never show `object`-symbols.") + }, + 'breadcrumbs.filteredTypes.key': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.key', "When set to `false` breadcrumbs never show `key`-symbols.") + }, + 'breadcrumbs.filteredTypes.null': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.null', "When set to `false` breadcrumbs never show `null`-symbols.") + }, + 'breadcrumbs.filteredTypes.enumMember': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.enumMember', "When set to `false` breadcrumbs never show `enumMember`-symbols.") + }, + 'breadcrumbs.filteredTypes.struct': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.struct', "When set to `false` breadcrumbs never show `struct`-symbols.") + }, + 'breadcrumbs.filteredTypes.event': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.event', "When set to `false` breadcrumbs never show `event`-symbols.") + }, + 'breadcrumbs.filteredTypes.operator': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.operator', "When set to `false` breadcrumbs never show `operator`-symbols.") + }, + 'breadcrumbs.filteredTypes.typeParameter': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.typeParameter', "When set to `false` breadcrumbs never show `typeParameter`-symbols.") } } }); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index fd1bec4610..8051d8445b 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -17,7 +17,7 @@ import 'vs/css!./media/breadcrumbscontrol'; import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; -import { symbolKindToCssClass } from 'vs/editor/common/modes'; +import { SymbolKinds } from 'vs/editor/common/modes'; import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { localize } from 'vs/nls'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; @@ -45,7 +45,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { onDidChangeZoomLevel } from 'vs/base/browser/browser'; -import { withNullAsUndefined } from 'vs/base/common/types'; +import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; import { ILabelService } from 'vs/platform/label/common/label'; class Item extends BreadcrumbsItem { @@ -109,7 +109,7 @@ class Item extends BreadcrumbsItem { // symbol if (this.options.showSymbolIcons) { let icon = document.createElement('div'); - icon.className = symbolKindToCssClass(this.element.symbol.kind); + icon.className = SymbolKinds.toCssClassName(this.element.symbol.kind); container.appendChild(icon); dom.addClass(container, 'shows-symbol-icon'); } @@ -130,15 +130,15 @@ export interface IBreadcrumbsControlOptions { export class BreadcrumbsControl { - static HEIGHT = 22; + static readonly HEIGHT = 22; static readonly Payload_Reveal = {}; static readonly Payload_RevealAside = {}; static readonly Payload_Pick = {}; - static CK_BreadcrumbsPossible = new RawContextKey('breadcrumbsPossible', false); - static CK_BreadcrumbsVisible = new RawContextKey('breadcrumbsVisible', false); - static CK_BreadcrumbsActive = new RawContextKey('breadcrumbsActive', false); + static readonly CK_BreadcrumbsPossible = new RawContextKey('breadcrumbsPossible', false); + static readonly CK_BreadcrumbsVisible = new RawContextKey('breadcrumbsVisible', false); + static readonly CK_BreadcrumbsActive = new RawContextKey('breadcrumbsActive', false); private readonly _ckBreadcrumbsPossible: IContextKey; private readonly _ckBreadcrumbsVisible: IContextKey; @@ -246,7 +246,7 @@ export class BreadcrumbsControl { const uri = input.getResource()!; const editor = this._getActiveCodeEditor(); - const model = new EditorBreadcrumbsModel(uri, editor, this._workspaceService, this._configurationService); + const model = new EditorBreadcrumbsModel(uri, editor, this._configurationService, this._workspaceService); dom.toggleClass(this.domNode, 'relative-path', model.isRelative()); dom.toggleClass(this.domNode, 'backslash-path', this._labelService.getSeparator(uri.scheme, uri.authority) === '\\'); @@ -485,7 +485,7 @@ export class BreadcrumbsControl { selection: Range.collapseToStart(element.symbol.selectionRange), revealInCenterIfOutsideViewport: true } - }, this._getActiveCodeEditor(), group === SIDE_GROUP); + }, withUndefinedAsNull(this._getActiveCodeEditor()), group === SIDE_GROUP); } } } diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts index da6c6d1f46..0b76064787 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts @@ -14,7 +14,7 @@ import { isEqual, dirname } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IPosition } from 'vs/editor/common/core/position'; -import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes'; +import { DocumentSymbolProviderRegistry, SymbolKinds } from 'vs/editor/common/modes'; import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { Schemas } from 'vs/base/common/network'; @@ -51,16 +51,15 @@ export class EditorBreadcrumbsModel { constructor( private readonly _uri: URI, private readonly _editor: ICodeEditor | undefined, + @IConfigurationService private readonly _configurationService: IConfigurationService, @IWorkspaceContextService workspaceService: IWorkspaceContextService, - @IConfigurationService configurationService: IConfigurationService, ) { - this._cfgFilePath = BreadcrumbsConfig.FilePath.bindTo(configurationService); - this._cfgSymbolPath = BreadcrumbsConfig.SymbolPath.bindTo(configurationService); + this._cfgFilePath = BreadcrumbsConfig.FilePath.bindTo(_configurationService); + this._cfgSymbolPath = BreadcrumbsConfig.SymbolPath.bindTo(_configurationService); this._disposables.add(this._cfgFilePath.onDidChange(_ => this._onDidUpdate.fire(this))); this._disposables.add(this._cfgSymbolPath.onDidChange(_ => this._onDidUpdate.fire(this))); - this._fileInfo = EditorBreadcrumbsModel._initFilePathInfo(this._uri, workspaceService); this._bindToEditor(); this._onDidUpdate.fire(this); @@ -138,6 +137,14 @@ export class EditorBreadcrumbsModel { this._disposables.add(this._editor.onDidChangeModel(_ => this._updateOutline())); this._disposables.add(this._editor.onDidChangeModelLanguage(_ => this._updateOutline())); + // update when config changes (re-render) + this._disposables.add(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('breadcrumbs.filteredTypes')) { + this._updateOutline(true); + } + })); + + // update soon'ish as model content change const updateSoon = new TimeoutTimer(); this._disposables.add(updateSoon); @@ -221,7 +228,18 @@ export class EditorBreadcrumbsModel { } item = parent; } - return chain.reverse(); + let result: Array = []; + for (let i = chain.length - 1; i >= 0; i--) { + let element = chain[i]; + if ( + element instanceof OutlineElement + && !this._configurationService.getValue(`breadcrumbs.filteredTypes.${SymbolKinds.toString(element.symbol.kind)}`) + ) { + break; + } + result.push(element); + } + return result; } private _updateOutlineElements(elements: Array): void { diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 4b85e3f51f..34614e1ae8 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -26,7 +26,7 @@ import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs import { BreadcrumbElement, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel'; import { IFileIconTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IAsyncDataSource, ITreeRenderer, ITreeNode, ITreeFilter, TreeVisibility, ITreeSorter } from 'vs/base/browser/ui/tree/tree'; -import { OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItemComparator, OutlineIdentityProvider, OutlineNavigationLabelProvider, OutlineDataSource, OutlineSortOrder } from 'vs/editor/contrib/documentSymbols/outlineTree'; +import { OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItemComparator, OutlineIdentityProvider, OutlineNavigationLabelProvider, OutlineDataSource, OutlineSortOrder, OutlineFilter } from 'vs/editor/contrib/documentSymbols/outlineTree'; import { IIdentityProvider, IListVirtualDelegate, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list'; export function createBreadcrumbsPicker(instantiationService: IInstantiationService, parent: HTMLElement, element: BreadcrumbElement): BreadcrumbsPicker { @@ -240,7 +240,7 @@ class FileRenderer implements ITreeRenderer, index: number, templateData: IResourceLabel): void { - const fileDecorations = this._configService.getValue<{ colors: boolean, badges: boolean }>('explorer.decorations'); + const fileDecorations = this._configService.getValue<{ colors: boolean, badges: boolean; }>('explorer.decorations'); const { element } = node; let resource: URI; let fileKind: FileKind; @@ -446,11 +446,13 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { [new OutlineGroupRenderer(), this._instantiationService.createInstance(OutlineElementRenderer)], new OutlineDataSource(), { + collapseByDefault: true, expandOnlyOnTwistieClick: true, multipleSelectionSupport: false, sorter: new OutlineItemComparator(this._getOutlineItemCompareType()), identityProvider: new OutlineIdentityProvider(), - keyboardNavigationLabelProvider: new OutlineNavigationLabelProvider() + keyboardNavigationLabelProvider: new OutlineNavigationLabelProvider(), + filter: this._instantiationService.createInstance(OutlineFilter, 'breadcrumbs.filteredTypes') } ); } diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index bd85031d9b..349287a214 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -17,7 +17,7 @@ import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorIn import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles'; import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor'; import { ChangeEncodingAction, ChangeEOLAction, ChangeModeAction, EditorStatus } from 'vs/workbench/browser/parts/editor/editorStatus'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; @@ -106,7 +106,7 @@ interface ISerializedUntitledEditorInput { resource: string; resourceJSON: object; modeId: string | undefined; - encoding: string; + encoding: string | undefined; } // Register Editor Input Factory @@ -223,7 +223,7 @@ class SideBySideEditorInputFactory implements IEditorInputFactory { Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(SideBySideEditorInput.ID, SideBySideEditorInputFactory); // Register Editor Contributions -registerEditorContribution(OpenWorkspaceButtonContribution); +registerEditorContribution(OpenWorkspaceButtonContribution.ID, OpenWorkspaceButtonContribution); // Register Editor Status Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(EditorStatus, LifecyclePhase.Ready); @@ -232,7 +232,10 @@ Registry.as(WorkbenchExtensions.Workbench).regi const registry = Registry.as(ActionExtensions.WorkbenchActions); registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_M) }), 'Change Language Mode'); registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeEOLAction, ChangeEOLAction.ID, ChangeEOLAction.LABEL), 'Change End of Line Sequence'); -registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL), 'Change File Encoding'); + +if (Object.keys(SUPPORTED_ENCODINGS).length > 1) { + registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL), 'Change File Encoding'); +} export class QuickOpenActionContributor extends ActionBarContributor { private openToSideActionInstance: OpenToSideFromQuickOpenAction | undefined; @@ -590,7 +593,7 @@ appendEditorToolItem( appendEditorToolItem( { id: editorCommands.TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, - title: nls.localize('ignoreTrimWhitespace.label', "Ignore Trim Whitespace"), + title: nls.localize('ignoreTrimWhitespace.label', "Ignore Leading/Trailing Whitespace Differences"), iconDark: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/paragraph-dark.svg')), iconLight: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/paragraph-light.svg')) }, @@ -602,7 +605,7 @@ appendEditorToolItem( appendEditorToolItem( { id: editorCommands.TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, - title: nls.localize('showTrimWhitespace.label', "Show Trim Whitespace"), + title: nls.localize('showTrimWhitespace.label', "Show Leading/Trailing Whitespace Differences"), iconDark: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/paragraph-disabled-dark.svg')), iconLight: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/paragraph-disabled-light.svg')) }, diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 6db48da459..6bd5a275e2 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -37,7 +37,8 @@ export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = { openSideBySideDirection: 'right', closeEmptyGroups: true, labelFormat: 'default', - iconTheme: 'vs-seti' + iconTheme: 'vs-seti', + splitSizing: 'distribute' }; export function impactsEditorPartOptions(event: IConfigurationChangeEvent): boolean { diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 8d85a1a61a..7d3f4e0f6d 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -495,7 +495,7 @@ export class CloseOneEditorAction extends Action { group = this.editorGroupService.getGroup(context.groupId); if (group) { - editorIndex = context.editorIndex!; // only allow editor at index if group is valid + editorIndex = context.editorIndex; // only allow editor at index if group is valid } } diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index d66cae2594..70736c4cee 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -606,6 +606,10 @@ function registerCloseEditorCommands() { .map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor); const editorsToClose = group.editors.filter(e => editors.indexOf(e) === -1); + if (group.activeEditor) { + group.pinEditor(group.activeEditor); + } + return group.closeEditors(editorsToClose); } @@ -624,6 +628,10 @@ function registerCloseEditorCommands() { const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context)); if (group && editor) { + if (group.activeEditor) { + group.pinEditor(group.activeEditor); + } + return group.closeEditors({ direction: CloseDirection.RIGHT, except: editor }); } @@ -744,7 +752,7 @@ export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsCon const selection: Array = list.getSelectedElements().filter(onlyEditorGroupAndEditor); // Only respect selection if it contains focused element - if (selection && selection.some(s => { + if (selection?.some(s => { if (isEditorGroup(s)) { return s.id === focus.groupId; } diff --git a/src/vs/workbench/browser/parts/editor/editorControl.ts b/src/vs/workbench/browser/parts/editor/editorControl.ts index 4941dbea18..ce8fd54436 100644 --- a/src/vs/workbench/browser/parts/editor/editorControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorControl.ts @@ -15,6 +15,7 @@ import { IEditorProgressService, LongRunningOperation } from 'vs/platform/progre 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'; +import { assertIsDefined } from 'vs/base/common/types'; export interface IOpenEditorResult { readonly control: BaseEditor; @@ -88,8 +89,9 @@ export class EditorControl extends Disposable { this.doSetActiveControl(control); // Show editor - this.parent.appendChild(control.getContainer()); - show(control.getContainer()); + const container = assertIsDefined(control.getContainer()); + this.parent.appendChild(container); + show(container); // Indicate to editor that it is now visible control.setVisible(true, this.groupView); @@ -154,7 +156,7 @@ export class EditorControl extends Disposable { // If the input did not change, return early and only apply the options // unless the options instruct us to force open it even if it is the same - const forceReload = options && options.forceReload; + const forceReload = options?.forceReload; const inputMatches = control.input && control.input.matches(editor); if (inputMatches && !forceReload) { @@ -203,8 +205,10 @@ export class EditorControl extends Disposable { // Remove control from parent and hide const controlInstanceContainer = this._activeControl.getContainer(); - this.parent.removeChild(controlInstanceContainer); - hide(controlInstanceContainer); + if (controlInstanceContainer) { + this.parent.removeChild(controlInstanceContainer); + hide(controlInstanceContainer); + } // Indicate to editor control this._activeControl.clearInput(); diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 4fde15e688..183f21d822 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -16,6 +16,7 @@ import { GroupDirection, MergeGroupMode } from 'vs/workbench/services/editor/com import { toDisposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { RunOnceScheduler } from 'vs/base/common/async'; +import { find } from 'vs/base/common/arrays'; interface IDropOperation { splitDirection?: GroupDirection; @@ -23,7 +24,7 @@ interface IDropOperation { class DropOverlay extends Themable { - private static OVERLAY_ID = 'monaco-workbench-editor-drop-overlay'; + private static readonly OVERLAY_ID = 'monaco-workbench-editor-drop-overlay'; private container!: HTMLElement; private overlay!: HTMLElement; @@ -84,7 +85,7 @@ class DropOverlay extends Themable { protected updateStyles(): void { // Overlay drop background - this.overlay.style.backgroundColor = this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND); + this.overlay.style.backgroundColor = this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND) || ''; // Overlay contrast border (if any) const activeContrastBorderColor = this.getColor(activeContrastBorder); @@ -108,7 +109,16 @@ class DropOverlay extends Themable { } // Find out if operation is valid - const isCopy = isDraggingGroup ? this.isCopyOperation(e) : isDraggingEditor ? this.isCopyOperation(e, this.editorTransfer.getData(DraggedEditorIdentifier.prototype)![0].identifier) : true; + let isCopy = true; + if (isDraggingGroup) { + isCopy = this.isCopyOperation(e); + } else if (isDraggingEditor) { + const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype); + if (Array.isArray(data)) { + isCopy = this.isCopyOperation(e, data[0].identifier); + } + } + if (!isCopy) { const sourceGroupView = this.findSourceGroupView(); if (sourceGroupView === this.groupView) { @@ -162,12 +172,18 @@ class DropOverlay extends Themable { // Check for group transfer if (this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype)) { - return this.accessor.getGroup(this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype)![0].identifier); + const data = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype); + if (Array.isArray(data)) { + return this.accessor.getGroup(data[0].identifier); + } } // Check for editor transfer else if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) { - return this.accessor.getGroup(this.editorTransfer.getData(DraggedEditorIdentifier.prototype)![0].identifier.groupId); + const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype); + if (Array.isArray(data)) { + return this.accessor.getGroup(data[0].identifier.groupId); + } } return undefined; @@ -189,69 +205,75 @@ class DropOverlay extends Themable { // Check for group transfer if (this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype)) { - const draggedEditorGroup = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype)![0].identifier; + const data = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype); + if (Array.isArray(data)) { + const draggedEditorGroup = data[0].identifier; - // Return if the drop is a no-op - const sourceGroup = this.accessor.getGroup(draggedEditorGroup); - if (sourceGroup) { - if (typeof splitDirection !== 'number' && sourceGroup === this.groupView) { - return; - } + // Return if the drop is a no-op + const sourceGroup = this.accessor.getGroup(draggedEditorGroup); + if (sourceGroup) { + if (typeof splitDirection !== 'number' && sourceGroup === this.groupView) { + return; + } - // Split to new group - let targetGroup: IEditorGroupView | undefined; - if (typeof splitDirection === 'number') { - if (this.isCopyOperation(event)) { - targetGroup = this.accessor.copyGroup(sourceGroup, this.groupView, splitDirection); - } else { - targetGroup = this.accessor.moveGroup(sourceGroup, this.groupView, splitDirection); + // Split to new group + let targetGroup: IEditorGroupView | undefined; + if (typeof splitDirection === 'number') { + if (this.isCopyOperation(event)) { + targetGroup = this.accessor.copyGroup(sourceGroup, this.groupView, splitDirection); + } else { + targetGroup = this.accessor.moveGroup(sourceGroup, this.groupView, splitDirection); + } + } + + // Merge into existing group + else { + if (this.isCopyOperation(event)) { + targetGroup = this.accessor.mergeGroup(sourceGroup, this.groupView, { mode: MergeGroupMode.COPY_EDITORS }); + } else { + targetGroup = this.accessor.mergeGroup(sourceGroup, this.groupView); + } + } + + if (targetGroup) { + this.accessor.activateGroup(targetGroup); } } - // Merge into existing group - else { - if (this.isCopyOperation(event)) { - targetGroup = this.accessor.mergeGroup(sourceGroup, this.groupView, { mode: MergeGroupMode.COPY_EDITORS }); - } else { - targetGroup = this.accessor.mergeGroup(sourceGroup, this.groupView); - } - } - - if (targetGroup) { - this.accessor.activateGroup(targetGroup); - } + this.groupTransfer.clearData(DraggedEditorGroupIdentifier.prototype); } - - this.groupTransfer.clearData(DraggedEditorGroupIdentifier.prototype); } // Check for editor transfer else if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) { - const draggedEditor = this.editorTransfer.getData(DraggedEditorIdentifier.prototype)![0].identifier; - const targetGroup = ensureTargetGroup(); + const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype); + if (Array.isArray(data)) { + const draggedEditor = data[0].identifier; + const targetGroup = ensureTargetGroup(); - // Return if the drop is a no-op - const sourceGroup = this.accessor.getGroup(draggedEditor.groupId); - if (sourceGroup) { - if (sourceGroup === targetGroup) { - return; + // Return if the drop is a no-op + const sourceGroup = this.accessor.getGroup(draggedEditor.groupId); + if (sourceGroup) { + if (sourceGroup === targetGroup) { + return; + } + + // Open in target group + const options = getActiveTextEditorOptions(sourceGroup, draggedEditor.editor, EditorOptions.create({ pinned: true })); + targetGroup.openEditor(draggedEditor.editor, options); + + // Ensure target has focus + targetGroup.focus(); + + // Close in source group unless we copy + const copyEditor = this.isCopyOperation(event, draggedEditor); + if (!copyEditor) { + sourceGroup.closeEditor(draggedEditor.editor); + } } - // Open in target group - const options = getActiveTextEditorOptions(sourceGroup, draggedEditor.editor, EditorOptions.create({ pinned: true })); - targetGroup.openEditor(draggedEditor.editor, options); - - // Ensure target has focus - targetGroup.focus(); - - // Close in source group unless we copy - const copyEditor = this.isCopyOperation(event, draggedEditor); - if (!copyEditor) { - sourceGroup.closeEditor(draggedEditor.editor); - } + this.editorTransfer.clearData(DraggedEditorIdentifier.prototype); } - - this.editorTransfer.clearData(DraggedEditorIdentifier.prototype); } // Check for URI transfer @@ -266,7 +288,7 @@ class DropOverlay extends Themable { } private isCopyOperation(e: DragEvent, draggedEditor?: IEditorIdentifier): boolean { - if (draggedEditor && draggedEditor.editor instanceof EditorInput && !draggedEditor.editor.supportsSplitEditor()) { + if (draggedEditor?.editor instanceof EditorInput && !draggedEditor.editor.supportsSplitEditor()) { return false; } @@ -438,6 +460,10 @@ class DropOverlay extends Themable { } } +export interface EditorDropTargetDelegate { + groupContainsPredicate?(groupView: IEditorGroupView): boolean; +} + export class EditorDropTarget extends Themable { private _overlay?: DropOverlay; @@ -450,6 +476,7 @@ export class EditorDropTarget extends Themable { constructor( private accessor: IEditorGroupsAccessor, private container: HTMLElement, + private readonly delegate: EditorDropTargetDelegate, @IThemeService themeService: IThemeService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { @@ -523,13 +550,7 @@ export class EditorDropTarget extends Themable { private findTargetGroupView(child: HTMLElement): IEditorGroupView | undefined { const groups = this.accessor.groups; - for (const groupView of groups) { - if (isAncestor(child, groupView.element)) { - return groupView; - } - } - - return undefined; + return find(groups, groupView => isAncestor(child, groupView.element) || this.delegate.groupContainsPredicate?.(groupView)); } private updateContainer(isDraggedOver: boolean): void { diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 9be335550b..b807a17e50 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -53,7 +53,8 @@ import { hash } from 'vs/base/common/hash'; import { guessMimeTypes } from 'vs/base/common/mime'; import { extname } from 'vs/base/common/resources'; import { Schemas } from 'vs/base/common/network'; -import { EditorActivation } from 'vs/platform/editor/common/editor'; +import { EditorActivation, EditorOpenContext } from 'vs/platform/editor/common/editor'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; export class EditorGroupView extends Themable implements IEditorGroupView { @@ -99,13 +100,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#endregion private _group: EditorGroup; - private _disposed: boolean; + private _disposed = false; - private active: boolean; - private dimension: Dimension; + private active: boolean | undefined; + private dimension: Dimension | undefined; private _whenRestored: Promise; - private isRestored: boolean; + private isRestored = false; private scopedInstantiationService: IInstantiationService; @@ -123,12 +124,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { constructor( private accessor: IEditorGroupsAccessor, - from: IEditorGroupView | ISerializedEditorGroup, + from: IEditorGroupView | ISerializedEditorGroup | null, private _index: number, @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, @INotificationService private readonly notificationService: INotificationService, + @IDialogService private readonly dialogService: IDialogService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, @IKeybindingService private readonly keybindingService: IKeybindingService, @@ -149,7 +151,68 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.disposedEditorsWorker = this._register(new RunOnceWorker(editors => this.handleDisposedEditors(editors), 0)); - this.create(); + //#region create() + { + // Container + addClasses(this.element, 'editor-group-container'); + + // Container listeners + this.registerContainerListeners(); + + // Container toolbar + this.createContainerToolbar(); + + // Container context menu + this.createContainerContextMenu(); + + // Letterpress container + const letterpressContainer = document.createElement('div'); + addClass(letterpressContainer, 'editor-group-letterpress'); + this.element.appendChild(letterpressContainer); + + // Progress bar + this.progressBar = this._register(new ProgressBar(this.element)); + this._register(attachProgressBarStyler(this.progressBar, this.themeService)); + this.progressBar.hide(); + + // Scoped services + const scopedContextKeyService = this._register(this.contextKeyService.createScoped(this.element)); + this.scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection( + [IContextKeyService, scopedContextKeyService], + [IEditorProgressService, new EditorProgressService(this.progressBar)] + )); + + // Context keys + this.handleGroupContextKeys(scopedContextKeyService); + + // Title container + this.titleContainer = document.createElement('div'); + addClass(this.titleContainer, 'title'); + this.element.appendChild(this.titleContainer); + + // Title control + this.titleAreaControl = this.createTitleAreaControl(); + + // Editor container + this.editorContainer = document.createElement('div'); + addClass(this.editorContainer, 'editor-container'); + this.element.appendChild(this.editorContainer); + + // Editor control + this.editorControl = this._register(this.scopedInstantiationService.createInstance(EditorControl, this.editorContainer, this)); + this._onDidChange.input = this.editorControl.onDidSizeConstraintsChange; + + // Track Focus + this.doTrackFocus(); + + // Update containers + this.updateTitleContainer(); + this.updateContainer(); + + // Update styles + this.updateStyles(); + } + //#endregion this._whenRestored = this.restoreEditors(from); this._whenRestored.then(() => this.isRestored = true); @@ -157,68 +220,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.registerListeners(); } - private create(): void { - - // Container - addClasses(this.element, 'editor-group-container'); - - // Container listeners - this.registerContainerListeners(); - - // Container toolbar - this.createContainerToolbar(); - - // Container context menu - this.createContainerContextMenu(); - - // Letterpress container - const letterpressContainer = document.createElement('div'); - addClass(letterpressContainer, 'editor-group-letterpress'); - this.element.appendChild(letterpressContainer); - - // Progress bar - this.progressBar = this._register(new ProgressBar(this.element)); - this._register(attachProgressBarStyler(this.progressBar, this.themeService)); - this.progressBar.hide(); - - // Scoped services - const scopedContextKeyService = this._register(this.contextKeyService.createScoped(this.element)); - this.scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection( - [IContextKeyService, scopedContextKeyService], - [IEditorProgressService, new EditorProgressService(this.progressBar)] - )); - - // Context keys - this.handleGroupContextKeys(scopedContextKeyService); - - // Title container - this.titleContainer = document.createElement('div'); - addClass(this.titleContainer, 'title'); - this.element.appendChild(this.titleContainer); - - // Title control - this.createTitleAreaControl(); - - // Editor container - this.editorContainer = document.createElement('div'); - addClass(this.editorContainer, 'editor-container'); - this.element.appendChild(this.editorContainer); - - // Editor control - this.editorControl = this._register(this.scopedInstantiationService.createInstance(EditorControl, this.editorContainer, this)); - this._onDidChange.input = this.editorControl.onDidSizeConstraintsChange; - - // Track Focus - this.doTrackFocus(); - - // Update containers - this.updateTitleContainer(); - this.updateContainer(); - - // Update styles - this.updateStyles(); - } - private handleGroupContextKeys(contextKeyService: IContextKeyService): void { const groupActiveEditorDirtyContextKey = EditorGroupActiveEditorDirtyContext.bindTo(contextKeyService); const groupEditorsCountContext = EditorGroupEditorsCountContext.bindTo(contextKeyService); @@ -295,7 +296,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const removeGroupAction = this._register(new Action( CLOSE_EDITOR_GROUP_COMMAND_ID, localize('closeGroupAction', "Close"), - 'close-editor-group', + 'codicon-close', true, () => { this.accessor.removeGroup(this); @@ -410,7 +411,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { toggleClass(this.titleContainer, 'show-file-icons', this.accessor.partOptions.showIcons); } - private createTitleAreaControl(): void { + private createTitleAreaControl(): TitleControl { // Clear old if existing if (this.titleAreaControl) { @@ -424,15 +425,17 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } else { this.titleAreaControl = this.scopedInstantiationService.createInstance(NoTabsTitleControl, this.titleContainer, this.accessor, this); } + + return this.titleAreaControl; } - private async restoreEditors(from: IEditorGroupView | ISerializedEditorGroup): Promise { - await this._group.removeNonExitingEditor(); // {{SQL CARBON EDIT}} @udeeshagautam perform async correction for non-existing files - + private async restoreEditors(from: IEditorGroupView | ISerializedEditorGroup | null): Promise { if (this._group.count === 0) { return; // nothing to show } + await this._group.removeNonExitingEditor(); // {{SQL CARBON EDIT}} @udeeshagautam perform async correction for non-existing files + // Determine editor options let options: EditorOptions; if (from instanceof EditorGroupView) { @@ -604,7 +607,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Recreate and layout control this.createTitleAreaControl(); - this.layoutTitleAreaControl(); + if (this.dimension) { + this.layoutTitleAreaControl(this.dimension.width); + } // Ensure to show active editor if any if (this._group.activeEditor) { @@ -829,7 +834,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Determine options const openEditorOptions: IEditorOpenOptions = { index: options ? options.index : undefined, - pinned: !this.accessor.partOptions.enablePreview || editor.isDirty() || (options && options.pinned) || (options && typeof options.index === 'number'), + pinned: !this.accessor.partOptions.enablePreview || editor.isDirty() || options?.pinned || typeof options?.index === 'number', active: this._group.count === 0 || !options || !options.inactive }; @@ -843,13 +848,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { let activateGroup = false; let restoreGroup = false; - if (options && options.activation === EditorActivation.ACTIVATE) { + if (options?.activation === EditorActivation.ACTIVATE) { // Respect option to force activate an editor group. activateGroup = true; - } else if (options && options.activation === EditorActivation.RESTORE) { + } else if (options?.activation === EditorActivation.RESTORE) { // Respect option to force restore an editor group. restoreGroup = true; - } else if (options && options.activation === EditorActivation.PRESERVE) { + } else if (options?.activation === EditorActivation.PRESERVE) { // Respect option to preserve active editor group. activateGroup = false; restoreGroup = false; @@ -921,23 +926,67 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return openEditorPromise; } - private doHandleOpenEditorError(error: Error, editor: EditorInput, options?: EditorOptions): void { + private async doHandleOpenEditorError(error: Error, editor: EditorInput, options?: EditorOptions): Promise { // Report error only if this was not us restoring previous error state or // we are told to ignore errors that occur from opening an editor if (this.isRestored && !isPromiseCanceledError(error) && (!options || !options.ignoreError)) { - const actions: INotificationActions = { primary: [] }; + + // Extract possible error actions from the error + let errorActions: ReadonlyArray | undefined = undefined; if (isErrorWithActions(error)) { - actions.primary = (error as IErrorWithActions).actions; + errorActions = (error as IErrorWithActions).actions; } - const handle = this.notificationService.notify({ - severity: Severity.Error, - message: localize('editorOpenError', "Unable to open '{0}': {1}.", editor.getName(), toErrorMessage(error)), - actions - }); + // If the context is USER, we try to show a modal dialog instead of a background notification + if (options?.context === EditorOpenContext.USER) { + const buttons: string[] = []; + if (Array.isArray(errorActions) && errorActions.length > 0) { + errorActions.forEach(action => buttons.push(action.label)); + } else { + buttons.push(localize('ok', 'OK')); + } - Event.once(handle.onDidClose)(() => actions.primary && dispose(actions.primary)); + let cancelId: number | undefined = undefined; + if (buttons.length === 1) { + buttons.push(localize('cancel', "Cancel")); + cancelId = 1; + } + + const result = await this.dialogService.show( + Severity.Error, + localize('editorOpenErrorDialog', "Unable to open '{0}'", editor.getName()), + buttons, + { + detail: toErrorMessage(error), + cancelId + } + ); + + // Make sure to run any error action if present + if (result.choice !== cancelId && Array.isArray(errorActions)) { + const errorAction = errorActions[result.choice]; + if (errorAction) { + errorAction.run(); + } + } + } + + // Otherwise, show a background notification. + else { + const actions: INotificationActions = { primary: [] }; + if (Array.isArray(errorActions)) { + actions.primary = errorActions; + } + + const handle = this.notificationService.notify({ + severity: Severity.Error, + message: localize('editorOpenError', "Unable to open '{0}': {1}.", editor.getName(), toErrorMessage(error)), + actions + }); + + Event.once(handle.onDidClose)(() => actions.primary && dispose(actions.primary)); + } } // Event @@ -1073,7 +1122,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } // Do close - this.doCloseEditor(editor, options && options.preserveFocus ? false : undefined); + this.doCloseEditor(editor, options?.preserveFocus ? false : undefined); } private doCloseEditor(editor: EditorInput, focusNext = (this.accessor.activeGroup === this), fromError?: boolean): void { @@ -1322,7 +1371,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Close active editor last if contained in editors list to close if (closeActiveEditor) { - this.doCloseActiveEditor(options && options.preserveFocus ? false : undefined); + this.doCloseActiveEditor(options?.preserveFocus ? false : undefined); } // Forward to title control @@ -1450,9 +1499,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Container if (isEmpty) { - this.element.style.backgroundColor = this.getColor(EDITOR_GROUP_EMPTY_BACKGROUND); + this.element.style.backgroundColor = this.getColor(EDITOR_GROUP_EMPTY_BACKGROUND) || ''; } else { - this.element.style.backgroundColor = null; + this.element.style.backgroundColor = ''; } // Title control @@ -1467,10 +1516,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.titleContainer.style.removeProperty('--title-border-bottom-color'); } - this.titleContainer.style.backgroundColor = this.getColor(showTabs ? EDITOR_GROUP_HEADER_TABS_BACKGROUND : EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND); + this.titleContainer.style.backgroundColor = this.getColor(showTabs ? EDITOR_GROUP_HEADER_TABS_BACKGROUND : EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND) || ''; // Editor container - this.editorContainer.style.backgroundColor = this.getColor(editorBackground); + this.editorContainer.style.backgroundColor = this.getColor(editorBackground) || ''; } //#endregion @@ -1495,12 +1544,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.editorContainer.style.height = `calc(100% - ${this.titleAreaControl.getPreferredHeight()}px)`; // Forward to controls - this.layoutTitleAreaControl(); + this.layoutTitleAreaControl(width); this.editorControl.layout(new Dimension(this.dimension.width, this.dimension.height - this.titleAreaControl.getPreferredHeight())); } - private layoutTitleAreaControl(): void { - this.titleAreaControl.layout(new Dimension(this.dimension.width, this.titleAreaControl.getPreferredHeight())); + private layoutTitleAreaControl(width: number): void { + this.titleAreaControl.layout(new Dimension(width, this.titleAreaControl.getPreferredHeight())); } relayout(): void { @@ -1528,7 +1577,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } class EditorOpeningEvent implements IEditorOpeningEvent { - private override: () => Promise; + private override: (() => Promise) | undefined = undefined; constructor( private _group: GroupIdentifier, @@ -1553,7 +1602,7 @@ class EditorOpeningEvent implements IEditorOpeningEvent { this.override = callback; } - isPrevented(): () => Promise { + isPrevented(): (() => Promise) | undefined { return this.override; } } diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index e8299b3a82..920f4ba7e5 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -23,13 +23,14 @@ import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/com import { assign } from 'vs/base/common/objects'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; -import { EditorDropTarget } from 'vs/workbench/browser/parts/editor/editorDropTarget'; +import { EditorDropTarget, EditorDropTargetDelegate } from 'vs/workbench/browser/parts/editor/editorDropTarget'; import { Color } from 'vs/base/common/color'; import { CenteredViewLayout } from 'vs/base/browser/ui/centered/centeredViewLayout'; 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'; +import { assertIsDefined } from 'vs/base/common/types'; interface IEditorPartUIState { serializedGrid: ISerializedGrid; @@ -49,13 +50,13 @@ class GridWidgetView implements IView { private _onDidChange = new Relay<{ width: number; height: number; } | undefined>(); readonly onDidChange: Event<{ width: number; height: number; } | undefined> = this._onDidChange.event; - private _gridWidget: Grid; + private _gridWidget: Grid | undefined; - get gridWidget(): Grid { + get gridWidget(): Grid | undefined { return this._gridWidget; } - set gridWidget(grid: Grid) { + set gridWidget(grid: Grid | undefined) { this.element.innerHTML = ''; if (grid) { @@ -113,9 +114,6 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro private _onDidSizeConstraintsChange = this._register(new Relay<{ width: number; height: number; } | undefined>()); get onDidSizeConstraintsChange(): Event<{ width: number; height: number; } | undefined> { return Event.any(this.onDidSetGridWidget.event, this._onDidSizeConstraintsChange.event); } - private _onDidVisibilityChange = this._register(new Emitter()); - readonly onDidVisibilityChange: Event = this._onDidVisibilityChange.event; - //#endregion private readonly workspaceMemento: MementoObject; @@ -123,17 +121,18 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro private _partOptions: IEditorPartOptions; - private _activeGroup: IEditorGroupView; private groupViews: Map = new Map(); private mostRecentActiveGroups: GroupIdentifier[] = []; - private container: HTMLElement; - private centeredLayoutWidget: CenteredViewLayout; - private gridWidget: SerializableGrid; + private container: HTMLElement | undefined; + + private centeredLayoutWidget!: CenteredViewLayout; + + private gridWidget!: SerializableGrid; private gridWidgetView: GridWidgetView; private _whenRestored: Promise; - private whenRestoredResolve: () => void; + private whenRestoredResolve: (() => void) | undefined; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -204,9 +203,10 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro //#region IEditorGroupsService - private _contentDimension: Dimension; + private _contentDimension!: Dimension; get contentDimension(): Dimension { return this._contentDimension; } + private _activeGroup!: IEditorGroupView; get activeGroup(): IEditorGroupView { return this._activeGroup; } @@ -463,7 +463,11 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro } } - private shouldRestoreFocus(target: Element): boolean { + private shouldRestoreFocus(target: Element | undefined): boolean { + if (!target) { + return false; + } + const activeElement = document.activeElement; if (activeElement === document.body) { @@ -490,7 +494,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro const group = this.doAddGroup(locationView, direction); - if (options && options.activate) { + if (options?.activate) { this.doSetGroupActive(group); } @@ -503,7 +507,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro // Add to grid widget this.gridWidget.addView( newGroupView, - Sizing.Distribute, + this.getSplitSizingStyle(), locationView, this.toGridViewDirection(direction), ); @@ -520,6 +524,10 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro return newGroupView; } + private getSplitSizingStyle(): Sizing { + return this._partOptions.splitSizing === 'split' ? Sizing.Split : Sizing.Distribute; + } + private doCreateGroupView(from?: IEditorGroupView | ISerializedEditorGroup | null): IEditorGroupView { // Create group view @@ -670,7 +678,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro } // Remove from grid widget & dispose - this.gridWidget.removeView(groupView, Sizing.Distribute); + this.gridWidget.removeView(groupView, this.getSplitSizingStyle()); groupView.dispose(); // Restore focus if we had it previously (we run this after gridWidget.removeView() is called @@ -700,7 +708,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro const restoreFocus = this.shouldRestoreFocus(sourceView.element); // Move through grid widget API - this.gridWidget.moveView(sourceView, Sizing.Distribute, targetView, this.toGridViewDirection(direction)); + this.gridWidget.moveView(sourceView, this.getSplitSizingStyle(), targetView, this.toGridViewDirection(direction)); // Restore focus if we had it previously (we run this after gridWidget.removeView() is called // because removing a view can mean to reparent it and thus focus would be removed otherwise) @@ -744,7 +752,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro const inactive = !sourceView.isActive(editor) || this._activeGroup !== sourceView; const copyOptions: ICopyEditorOptions = { index, inactive, preserveFocus: inactive }; - if (options && options.mode === MergeGroupMode.COPY_EDITORS) { + if (options?.mode === MergeGroupMode.COPY_EDITORS) { sourceView.copyEditor(editor, targetView, copyOptions); } else { sourceView.moveEditor(editor, targetView, copyOptions); @@ -796,7 +804,8 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro } updateStyles(): void { - this.container.style.backgroundColor = this.getColor(editorBackground); + const container = assertIsDefined(this.container); + container.style.backgroundColor = this.getColor(editorBackground) || ''; const separatorBorderStyle = { separatorBorder: this.gridSeparatorBorder, background: this.theme.getColor(EDITOR_PANE_BACKGROUND) || Color.transparent }; this.gridWidget.style(separatorBorderStyle); @@ -817,7 +826,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro this.centeredLayoutWidget = this._register(new CenteredViewLayout(this.container, this.gridWidgetView, this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY])); // Drop support - this._register(this.instantiationService.createInstance(EditorDropTarget, this, this.container)); + this._register(this.createEditorDropTarget(this.container, {})); return this.container; } @@ -850,7 +859,11 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro } // Signal restored - Promise.all(this.groups.map(group => group.whenRestored)).finally(() => this.whenRestoredResolve()); + Promise.all(this.groups.map(group => group.whenRestored)).finally(() => { + if (this.whenRestoredResolve) { + this.whenRestoredResolve(); + } + }); // Update container this.updateContainer(); @@ -861,7 +874,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro private doCreateGridControlWithPreviousState(): boolean { const uiState: IEditorPartUIState = this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY]; - if (uiState && uiState.serializedGrid) { + if (uiState?.serializedGrid) { try { // MRU @@ -950,7 +963,8 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro } private updateContainer(): void { - toggleClass(this.container, 'empty', this.isEmpty); + const container = assertIsDefined(this.container); + toggleClass(container, 'empty', this.isEmpty); } private notifyGroupIndexChange(): void { @@ -1024,15 +1038,19 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro //#endregion - setVisible(visible: boolean): void { - this._onDidVisibilityChange.fire(visible); - } - toJSON(): object { return { type: Parts.EDITOR_PART }; } + + //#region TODO@matt this should move into some kind of service + + createEditorDropTarget(container: HTMLElement, delegate: EditorDropTargetDelegate): IDisposable { + return this.instantiationService.createInstance(EditorDropTarget, this, container, delegate); + } + + //#endregion } registerSingleton(IEditorGroupsService, EditorPart); diff --git a/src/vs/workbench/browser/parts/editor/editorPicker.ts b/src/vs/workbench/browser/parts/editor/editorPicker.ts index 52ec086418..b50c12c65f 100644 --- a/src/vs/workbench/browser/parts/editor/editorPicker.ts +++ b/src/vs/workbench/browser/parts/editor/editorPicker.ts @@ -15,7 +15,7 @@ import { QuickOpenHandler } from 'vs/workbench/browser/quickopen'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService, IEditorGroup, EditorsOrder, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { EditorInput, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; +import { toResource, SideBySideEditor, IEditorInput } from 'vs/workbench/common/editor'; import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; import { CancellationToken } from 'vs/base/common/cancellation'; import { withNullAsUndefined } from 'vs/base/common/types'; @@ -23,7 +23,7 @@ import { withNullAsUndefined } from 'vs/base/common/types'; export class EditorPickerEntry extends QuickOpenEntryGroup { constructor( - private editor: EditorInput, + private editor: IEditorInput, private _group: IEditorGroup, @IModeService private readonly modeService: IModeService, @IModelService private readonly modelService: IModelService diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 12852898c4..095433bc24 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/editorstatus'; import * as nls from 'vs/nls'; import { runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { format } from 'vs/base/common/strings'; -import { extname, basename } from 'vs/base/common/resources'; +import { extname, basename, isEqual } from 'vs/base/common/resources'; import { areFunctions, withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { Action } from 'vs/base/common/actions'; @@ -39,7 +39,7 @@ import { ConfigurationChangedEvent, IEditorOptions, EditorOption } from 'vs/edit import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { deepClone } from 'vs/base/common/objects'; -import { ICodeEditor, isCodeEditor, isDiffEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { Schemas } from 'vs/base/common/network'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; @@ -57,7 +57,7 @@ import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/q class SideBySideEditorEncodingSupport implements IEncodingSupport { constructor(private master: IEncodingSupport, private details: IEncodingSupport) { } - getEncoding(): string { + getEncoding(): string | undefined { return this.master.getEncoding(); // always report from modified (right hand) side } @@ -281,7 +281,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { private readonly screenRedearModeElement = this._register(new MutableDisposable()); private readonly indentationElement = this._register(new MutableDisposable()); private readonly selectionElement = this._register(new MutableDisposable()); - private readonly encodingElement = this._register(new MutableDisposable()); + private readonly encodingElement = Object.keys(SUPPORTED_ENCODINGS).length > 1 ? this._register(new MutableDisposable()) : undefined; private readonly eolElement = this._register(new MutableDisposable()); private readonly modeElement = this._register(new MutableDisposable()); private readonly metadataElement = this._register(new MutableDisposable()); @@ -355,7 +355,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { return this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]); } - const picks: QuickPickInput[] = [ + const picks: QuickPickInput[] = [ activeTextEditorWidget.getAction(IndentUsingSpaces.ID), activeTextEditorWidget.getAction(IndentUsingTabs.ID), activeTextEditorWidget.getAction(DetectIndentation.ID), @@ -366,7 +366,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { return { id: a.id, label: a.label, - detail: Language.isDefaultVariant() ? undefined : a.alias, + detail: (Language.isDefaultVariant() || a.label === a.alias) ? undefined : a.alias, run: () => { activeTextEditorWidget.focus(); a.run(); @@ -378,7 +378,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { 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(); + return action?.run(); } private updateTabFocusModeElement(visible: boolean): void { @@ -440,6 +440,10 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } private updateEncodingElement(text: string | undefined): void { + if (!this.encodingElement) { + return; // return early if encoding should not show (e.g. in Web we only support utf8) + } + if (!text) { this.encodingElement.clear(); return; @@ -573,7 +577,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { this.onSelectionChange(activeCodeEditor); this.onModeChange(activeCodeEditor); this.onEOLChange(activeCodeEditor); - this.onEncodingChange(activeControl); + this.onEncodingChange(activeControl, activeCodeEditor); this.onIndentationChange(activeCodeEditor); this.onMetadataChange(activeControl); @@ -733,20 +737,23 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { const textModel = editorWidget.getModel(); if (textModel) { info.selections.forEach(selection => { - info.charactersSelected! += textModel.getValueLengthInRange(selection); + if (typeof info.charactersSelected !== 'number') { + info.charactersSelected = 0; + } + + info.charactersSelected += textModel.getCharacterCountInRange(selection); }); } // Compute the visible column for one selection. This will properly handle tabs and their configured widths if (info.selections.length === 1) { - const visibleColumn = editorWidget.getVisibleColumnFromPosition(editorWidget.getPosition()!); + const editorPosition = editorWidget.getPosition(); - let selectionClone = info.selections[0].clone(); // do not modify the original position we got from the editor - selectionClone = new Selection( - selectionClone.selectionStartLineNumber, - selectionClone.selectionStartColumn, - selectionClone.positionLineNumber, - visibleColumn + let selectionClone = new Selection( + info.selections[0].selectionStartLineNumber, + info.selections[0].selectionStartColumn, + info.selections[0].positionLineNumber, + editorPosition ? editorWidget.getStatusbarColumn(editorPosition) : info.selections[0].positionColumn ); info.selections[0] = selectionClone; @@ -769,19 +776,21 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { this.updateState(info); } - private onEncodingChange(e?: IBaseEditor): void { - if (e && !this.isActiveEditor(e)) { + private onEncodingChange(editor: IBaseEditor | undefined, editorWidget: ICodeEditor | undefined): void { + if (editor && !this.isActiveEditor(editor)) { return; } const info: StateDelta = { encoding: undefined }; - // We only support text based editors - if (e && (isCodeEditor(e.getControl()) || isDiffEditor(e.getControl()))) { - const encodingSupport: IEncodingSupport | null = e.input ? toEditorWithEncodingSupport(e.input) : null; + // We only support text based editors that have a model associated + // This ensures we do not show the encoding picker while an editor + // is still loading. + if (editor && editorWidget?.hasModel()) { + const encodingSupport: IEncodingSupport | null = editor.input ? toEditorWithEncodingSupport(editor.input) : null; if (encodingSupport) { const rawEncoding = encodingSupport.getEncoding(); - const encodingInfo = SUPPORTED_ENCODINGS[rawEncoding]; + const encodingInfo = typeof rawEncoding === 'string' ? SUPPORTED_ENCODINGS[rawEncoding] : undefined; if (encodingInfo) { info.encoding = encodingInfo.labelShort; // if we have a label, take it from there } else { @@ -797,8 +806,10 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { const activeControl = this.editorService.activeControl; if (activeControl) { const activeResource = toResource(activeControl.input, { supportSideBySide: SideBySideEditor.MASTER }); - if (activeResource && activeResource.toString() === resource.toString()) { - return this.onEncodingChange(activeControl); // only update if the encoding changed for the active resource + if (activeResource && isEqual(activeResource, resource)) { + const activeCodeEditor = withNullAsUndefined(getCodeEditor(activeControl.getControl())); + + return this.onEncodingChange(activeControl, activeCodeEditor); // only update if the encoding changed for the active resource } } } @@ -877,7 +888,7 @@ export class ChangeModeAction extends Action { const resource = this.editorService.activeEditor ? toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : null; let hasLanguageSupport = !!resource; - if (resource && resource.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource)) { + if (resource?.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource)) { hasLanguageSupport = false; // no configuration for untitled resources (e.g. "Untitled-1") } @@ -1041,11 +1052,11 @@ export class ChangeModeAction extends Action { let fakeResource: URI | undefined; const extensions = this.modeService.getExtensions(lang); - if (extensions && extensions.length) { + if (extensions?.length) { fakeResource = URI.file(extensions[0]); } else { const filenames = this.modeService.getFilenames(lang); - if (filenames && filenames.length) { + if (filenames?.length) { fakeResource = URI.file(filenames[0]); } } @@ -1089,12 +1100,12 @@ export class ChangeEOLAction extends Action { { label: nlsEOLCRLF, eol: EndOfLineSequence.CRLF }, ]; - const selectedIndex = (textModel && textModel.getEOL() === '\n') ? 0 : 1; + const selectedIndex = (textModel?.getEOL() === '\n') ? 0 : 1; const eol = await this.quickInputService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), activeItem: EOLOptions[selectedIndex] }); if (eol) { const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorWidget); - if (activeCodeEditor && activeCodeEditor.hasModel() && isWritableCodeEditor(activeCodeEditor)) { + if (activeCodeEditor?.hasModel() && isWritableCodeEditor(activeCodeEditor)) { textModel = activeCodeEditor.getModel(); textModel.pushEOL(eol.eol); } @@ -1134,14 +1145,19 @@ export class ChangeEncodingAction extends Action { return this.quickInputService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]); } - let saveWithEncodingPick: IQuickPickItem; - let reopenWithEncodingPick: IQuickPickItem; - if (Language.isDefaultVariant()) { - saveWithEncodingPick = { label: nls.localize('saveWithEncoding', "Save with Encoding") }; - reopenWithEncodingPick = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding") }; - } else { - saveWithEncodingPick = { label: nls.localize('saveWithEncoding', "Save with Encoding"), detail: 'Save with Encoding', }; - reopenWithEncodingPick = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding"), detail: 'Reopen with Encoding' }; + const saveWithEncodingPick: IQuickPickItem = { label: nls.localize('saveWithEncoding', "Save with Encoding") }; + const reopenWithEncodingPick: IQuickPickItem = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding") }; + + if (!Language.isDefaultVariant()) { + const saveWithEncodingAlias = 'Save with Encoding'; + if (saveWithEncodingAlias !== saveWithEncodingPick.label) { + saveWithEncodingPick.detail = saveWithEncodingAlias; + } + + const reopenWithEncodingAlias = 'Reopen with Encoding'; + if (reopenWithEncodingAlias !== reopenWithEncodingPick.label) { + reopenWithEncodingPick.detail = reopenWithEncodingAlias; + } } let action: IQuickPickItem; diff --git a/src/vs/workbench/browser/parts/editor/editorWidgets.ts b/src/vs/workbench/browser/parts/editor/editorWidgets.ts index 941bba4ea5..86043b0f0a 100644 --- a/src/vs/workbench/browser/parts/editor/editorWidgets.ts +++ b/src/vs/workbench/browser/parts/editor/editorWidgets.ts @@ -8,7 +8,7 @@ import { IOverlayWidget, ICodeEditor, IOverlayWidgetPosition, OverlayWidgetPosit import { Event, Emitter } from 'vs/base/common/event'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { $, append } from 'vs/base/browser/dom'; +import { $, append, clearNode } from 'vs/base/browser/dom'; import { attachStylerCallback } from 'vs/platform/theme/common/styler'; import { buttonBackground, buttonForeground, editorBackground, editorForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -31,12 +31,14 @@ export class FloatingClickWidget extends Widget implements IOverlayWidget { constructor( private editor: ICodeEditor, private label: string, - keyBindingAction: string, + keyBindingAction: string | null, @IKeybindingService keybindingService: IKeybindingService, @IThemeService private readonly themeService: IThemeService ) { super(); + this._domNode = $('.floating-click-widget'); + if (keyBindingAction) { const keybinding = keybindingService.lookupKeybinding(keyBindingAction); if (keybinding) { @@ -60,7 +62,7 @@ export class FloatingClickWidget extends Widget implements IOverlayWidget { } render() { - this._domNode = $('.floating-click-widget'); + clearNode(this._domNode); this._register(attachStylerCallback(this.themeService, { buttonBackground, buttonForeground, editorBackground, editorForeground, contrastBorder }, colors => { const backgroundColor = colors.buttonBackground ? colors.buttonBackground : colors.editorBackground; @@ -73,9 +75,9 @@ export class FloatingClickWidget extends Widget implements IOverlayWidget { this._domNode.style.color = foregroundColor.toString(); } - const borderColor = colors.contrastBorder ? colors.contrastBorder.toString() : null; - this._domNode.style.borderWidth = borderColor ? '1px' : null; - this._domNode.style.borderStyle = borderColor ? 'solid' : null; + const borderColor = colors.contrastBorder ? colors.contrastBorder.toString() : ''; + this._domNode.style.borderWidth = borderColor ? '1px' : ''; + this._domNode.style.borderStyle = borderColor ? 'solid' : ''; this._domNode.style.borderColor = borderColor; })); @@ -99,7 +101,7 @@ export class OpenWorkspaceButtonContribution extends Disposable implements IEdit return editor.getContribution(OpenWorkspaceButtonContribution.ID); } - private static readonly ID = 'editor.contrib.openWorkspaceButton'; + public static readonly ID = 'editor.contrib.openWorkspaceButton'; private openWorkspaceButton: FloatingClickWidget | undefined; @@ -120,10 +122,6 @@ export class OpenWorkspaceButtonContribution extends Disposable implements IEdit this._register(this.editor.onDidChangeModel(e => this.update())); } - getId(): string { - return OpenWorkspaceButtonContribution.ID; - } - private update(): void { if (!this.shouldShowButton(this.editor)) { this.disposeOpenWorkspaceWidgetRenderer(); diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index adaaa2453a..45c48cc9bf 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -186,7 +186,7 @@ overflow: visible; /* ...but still show the close button on hover, focus and when dirty */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off > .tab-close { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off:not(.dirty) > .tab-close { display: none; /* hide the close action bar when we are configured to hide it */ } @@ -232,11 +232,16 @@ padding-right: 5px; /* we need less room when sizing is shrink */ } +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty-border-top > .tab-close { + display: none; /* hide dirty state when highlightModifiedTabs is enabled and when running without close button */ +} + .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty:not(.dirty-border-top) { - background-repeat: no-repeat; - background-position-y: center; - background-position-x: calc(100% - 6px); /* to the right of the tab label */ - padding-right: 28px; /* make room for dirty indication when we are running without close button */ + padding-right: 0; /* remove extra padding when we are running without close button */ +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off > .tab-close { + pointer-events: none; /* don't allow dirty state/close button to be clicked when running without close button */ } /* Editor Actions */ diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts index 453a42375c..9737270cbb 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -14,7 +14,7 @@ import { EDITOR_TITLE_HEIGHT } from 'vs/workbench/browser/parts/editor/editor'; import { IAction } from 'vs/base/common/actions'; import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { Color } from 'vs/base/common/color'; -import { withNullAsUndefined } from 'vs/base/common/types'; +import { withNullAsUndefined, assertIsDefined, assertAllDefined } from 'vs/base/common/types'; interface IRenderedEditorLabel { editor?: IEditorInput; @@ -22,23 +22,23 @@ interface IRenderedEditorLabel { } export class NoTabsTitleControl extends TitleControl { - private titleContainer: HTMLElement; - private editorLabel: IResourceLabel; + private titleContainer: HTMLElement | undefined; + private editorLabel: IResourceLabel | undefined; private activeLabel: IRenderedEditorLabel = Object.create(null); protected create(parent: HTMLElement): void { - this.titleContainer = parent; - this.titleContainer.draggable = true; + const titleContainer = this.titleContainer = parent; + titleContainer.draggable = true; //Container listeners - this.registerContainerListeners(); + this.registerContainerListeners(titleContainer); // Gesture Support - this._register(Gesture.addTarget(this.titleContainer)); + this._register(Gesture.addTarget(titleContainer)); const labelContainer = document.createElement('div'); addClass(labelContainer, 'label-container'); - this.titleContainer.appendChild(labelContainer); + titleContainer.appendChild(labelContainer); // Editor Label this.editorLabel = this._register(this.instantiationService.createInstance(ResourceLabel, labelContainer, undefined)).element; @@ -46,41 +46,41 @@ 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._register({ dispose: () => removeClass(this.titleContainer, 'breadcrumbs') }); // import to remove because the container is a shared dom node + toggleClass(titleContainer, 'breadcrumbs', Boolean(this.breadcrumbsControl)); + this._register({ dispose: () => removeClass(titleContainer, 'breadcrumbs') }); // import to remove because the container is a shared dom node // Right Actions Container const actionsContainer = document.createElement('div'); addClass(actionsContainer, 'title-actions'); - this.titleContainer.appendChild(actionsContainer); + titleContainer.appendChild(actionsContainer); // Editor actions toolbar this.createEditorActionsToolBar(actionsContainer); } - private registerContainerListeners(): void { + private registerContainerListeners(titleContainer: HTMLElement): void { // Group dragging - this.enableGroupDragging(this.titleContainer); + this.enableGroupDragging(titleContainer); // Pin on double click - this._register(addDisposableListener(this.titleContainer, EventType.DBLCLICK, (e: MouseEvent) => this.onTitleDoubleClick(e))); + this._register(addDisposableListener(titleContainer, EventType.DBLCLICK, (e: MouseEvent) => this.onTitleDoubleClick(e))); // Detect mouse click - this._register(addDisposableListener(this.titleContainer, EventType.MOUSE_UP, (e: MouseEvent) => this.onTitleClick(e))); + this._register(addDisposableListener(titleContainer, EventType.MOUSE_UP, (e: MouseEvent) => this.onTitleClick(e))); // Detect touch - this._register(addDisposableListener(this.titleContainer, TouchEventType.Tap, (e: GestureEvent) => this.onTitleClick(e))); + this._register(addDisposableListener(titleContainer, TouchEventType.Tap, (e: GestureEvent) => this.onTitleClick(e))); // Context Menu - this._register(addDisposableListener(this.titleContainer, EventType.CONTEXT_MENU, (e: Event) => { + this._register(addDisposableListener(titleContainer, EventType.CONTEXT_MENU, (e: Event) => { if (this.group.activeEditor) { - this.onContextMenu(this.group.activeEditor, e, this.titleContainer); + this.onContextMenu(this.group.activeEditor, e, titleContainer); } })); - this._register(addDisposableListener(this.titleContainer, TouchEventType.Contextmenu, (e: Event) => { + this._register(addDisposableListener(titleContainer, TouchEventType.Contextmenu, (e: Event) => { if (this.group.activeEditor) { - this.onContextMenu(this.group.activeEditor, e, this.titleContainer); + this.onContextMenu(this.group.activeEditor, e, titleContainer); } })); } @@ -157,10 +157,11 @@ export class NoTabsTitleControl extends TitleControl { updateEditorDirty(editor: IEditorInput): void { this.ifEditorIsActive(editor, () => { + const titleContainer = assertIsDefined(this.titleContainer); if (editor.isDirty()) { - addClass(this.titleContainer, 'dirty'); + addClass(titleContainer, 'dirty'); } else { - removeClass(this.titleContainer, 'dirty'); + removeClass(titleContainer, 'dirty'); } }); } @@ -176,7 +177,9 @@ export class NoTabsTitleControl extends TitleControl { } protected handleBreadcrumbsEnablementChange(): void { - toggleClass(this.titleContainer, 'breadcrumbs', Boolean(this.breadcrumbsControl)); + const titleContainer = assertIsDefined(this.titleContainer); + + toggleClass(titleContainer, 'breadcrumbs', Boolean(this.breadcrumbsControl)); this.redraw(); } @@ -230,9 +233,10 @@ export class NoTabsTitleControl extends TitleControl { } // Clear if there is no editor + const [titleContainer, editorLabel] = assertAllDefined(this.titleContainer, this.editorLabel); if (!editor) { - removeClass(this.titleContainer, 'dirty'); - this.editorLabel.clear(); + removeClass(titleContainer, 'dirty'); + editorLabel.clear(); this.clearEditorActionsToolbar(); } @@ -261,11 +265,11 @@ export class NoTabsTitleControl extends TitleControl { title = ''; // dont repeat what is already shown } - this.editorLabel.setResource({ name, description, resource: resource || undefined }, { title: typeof title === 'string' ? title : undefined, italic: !isEditorPinned, extraClasses: ['no-tabs', 'title-label'] }); + editorLabel.setResource({ name, description, resource: resource || undefined }, { title: typeof title === 'string' ? title : undefined, italic: !isEditorPinned, extraClasses: ['no-tabs', 'title-label'] }); if (isGroupActive) { - this.editorLabel.element.style.color = this.getColor(TAB_ACTIVE_FOREGROUND); + editorLabel.element.style.color = this.getColor(TAB_ACTIVE_FOREGROUND); } else { - this.editorLabel.element.style.color = this.getColor(TAB_UNFOCUSED_ACTIVE_FOREGROUND); + editorLabel.element.style.color = this.getColor(TAB_UNFOCUSED_ACTIVE_FOREGROUND); } // Update Editor Actions Toolbar diff --git a/src/vs/workbench/browser/parts/editor/resourceViewer.ts b/src/vs/workbench/browser/parts/editor/resourceViewer.ts index e8cfb5916a..fc65d4ac65 100644 --- a/src/vs/workbench/browser/parts/editor/resourceViewer.ts +++ b/src/vs/workbench/browser/parts/editor/resourceViewer.ts @@ -16,7 +16,7 @@ import { IMAGE_PREVIEW_BORDER } from 'vs/workbench/common/theme'; export interface IResourceDescriptor { readonly resource: URI; readonly name: string; - readonly size: number; + readonly size?: number; readonly etag?: string; readonly mime: string; } @@ -82,8 +82,8 @@ export class ResourceViewer { container.className = 'monaco-resource-viewer'; // Large Files - if (descriptor.size > ResourceViewer.MAX_OPEN_INTERNAL_SIZE) { - return FileTooLargeFileView.create(container, descriptor, scrollbar, delegate); + if (typeof descriptor.size === 'number' && descriptor.size > ResourceViewer.MAX_OPEN_INTERNAL_SIZE) { + return FileTooLargeFileView.create(container, descriptor.size, scrollbar, delegate); } // Seemingly Binary Files @@ -96,11 +96,11 @@ export class ResourceViewer { class FileTooLargeFileView { static create( container: HTMLElement, - descriptor: IResourceDescriptor, + descriptorSize: number, scrollbar: DomScrollableElement, delegate: ResourceViewerDelegate ) { - const size = BinarySize.formatSize(descriptor.size); + const size = BinarySize.formatSize(descriptorSize); delegate.metadataClb(size); DOM.clearNode(container); diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index efd9b69653..c4442dc1e6 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts @@ -17,6 +17,7 @@ import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsSe import { SplitView, Sizing, Orientation } from 'vs/base/browser/ui/splitview/splitview'; import { Event, Relay, Emitter } from 'vs/base/common/event'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { assertIsDefined } from 'vs/base/common/types'; export class SideBySideEditor extends BaseEditor { @@ -47,10 +48,10 @@ export class SideBySideEditor extends BaseEditor { protected masterEditor?: BaseEditor; protected detailsEditor?: BaseEditor; - private masterEditorContainer: HTMLElement; - private detailsEditorContainer: HTMLElement; + private masterEditorContainer: HTMLElement | undefined; + private detailsEditorContainer: HTMLElement | undefined; - private splitview: SplitView; + private splitview: SplitView | undefined; private dimension: DOM.Dimension = new DOM.Dimension(0, 0); private onDidCreateEditors = this._register(new Emitter<{ width: number; height: number; } | undefined>()); @@ -69,8 +70,8 @@ export class SideBySideEditor extends BaseEditor { protected createEditor(parent: HTMLElement): void { DOM.addClass(parent, 'side-by-side-editor'); - this.splitview = this._register(new SplitView(parent, { orientation: Orientation.HORIZONTAL })); - this._register(this.splitview.onDidSashReset(() => this.splitview.distributeViewSizes())); + const splitview = this.splitview = this._register(new SplitView(parent, { orientation: Orientation.HORIZONTAL })); + this._register(this.splitview.onDidSashReset(() => splitview.distributeViewSizes())); this.detailsEditorContainer = DOM.$('.details-editor-container'); this.splitview.addView({ @@ -140,7 +141,9 @@ export class SideBySideEditor extends BaseEditor { layout(dimension: DOM.Dimension): void { this.dimension = dimension; - this.splitview.layout(dimension.width); + + const splitview = assertIsDefined(this.splitview); + splitview.layout(dimension.width); } getControl(): IEditorControl | undefined { @@ -179,8 +182,8 @@ export class SideBySideEditor extends BaseEditor { } private setNewInput(newInput: SideBySideEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { - const detailsEditor = this.doCreateEditor(newInput.details, this.detailsEditorContainer); - const masterEditor = this.doCreateEditor(newInput.master, this.masterEditorContainer); + const detailsEditor = this.doCreateEditor(newInput.details, assertIsDefined(this.detailsEditorContainer)); + const masterEditor = this.doCreateEditor(newInput.master, assertIsDefined(this.masterEditorContainer)); return this.onEditorsCreated(detailsEditor, masterEditor, newInput.details, newInput.master, options, token); } @@ -234,8 +237,13 @@ export class SideBySideEditor extends BaseEditor { this.masterEditor = undefined; } - this.detailsEditorContainer.innerHTML = ''; - this.masterEditorContainer.innerHTML = ''; + if (this.detailsEditorContainer) { + DOM.clearNode(this.detailsEditorContainer); + } + + if (this.masterEditorContainer) { + DOM.clearNode(this.masterEditorContainer); + } } dispose(): void { diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index f5b74a10f4..c196e730ee 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -40,7 +40,7 @@ import { CloseOneEditorAction } from 'vs/workbench/browser/parts/editor/editorAc import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { BreadcrumbsControl } from 'vs/workbench/browser/parts/editor/breadcrumbsControl'; import { IFileService } from 'vs/platform/files/common/files'; -import { withNullAsUndefined } from 'vs/base/common/types'; +import { withNullAsUndefined, assertAllDefined, assertIsDefined } from 'vs/base/common/types'; import { ILabelService } from 'vs/platform/label/common/label'; // {{SQL CARBON EDIT}} -- Display the editor's tab color @@ -51,7 +51,7 @@ import { GlobalNewUntitledFileAction } from 'vs/workbench/contrib/files/browser/ // {{SQL CARBON EDIT}} -- End interface IEditorInputLabel { - name: string; + name?: string; description?: string; title?: string; } @@ -60,19 +60,20 @@ type AugmentedLabel = IEditorInputLabel & { editor: IEditorInput }; export class TabsTitleControl extends TitleControl { - private titleContainer: HTMLElement; - private tabsContainer: HTMLElement; - private editorToolbarContainer: HTMLElement; - private tabsScrollbar: ScrollableElement; + private titleContainer: HTMLElement | undefined; + private tabsContainer: HTMLElement | undefined; + private editorToolbarContainer: HTMLElement | undefined; + private tabsScrollbar: ScrollableElement | undefined; + private closeOneEditorAction: CloseOneEditorAction; private tabResourceLabels: ResourceLabels; private tabLabels: IEditorInputLabel[] = []; private tabDisposables: IDisposable[] = []; - private dimension: Dimension; + private dimension: Dimension | undefined; private readonly layoutScheduled = this._register(new MutableDisposable()); - private blockRevealActiveTab: boolean; + private blockRevealActiveTab: boolean | undefined; constructor( parent: HTMLElement, @@ -97,6 +98,9 @@ export class TabsTitleControl extends TitleControl { @ILabelService labelService: ILabelService ) { super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickOpenService, themeService, extensionService, configurationService, fileService, labelService); + + this.tabResourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER)); + this.closeOneEditorAction = this._register(this.instantiationService.createInstance(CloseOneEditorAction, CloseOneEditorAction.ID, CloseOneEditorAction.LABEL)); } protected create(parent: HTMLElement): void { @@ -113,13 +117,13 @@ export class TabsTitleControl extends TitleControl { this.tabsContainer.draggable = true; addClass(this.tabsContainer, 'tabs-container'); - // Tabs Container listeners - this.registerTabsContainerListeners(); - // Tabs Scrollbar this.tabsScrollbar = this._register(this.createTabsScrollbar(this.tabsContainer)); tabsAndActionsContainer.appendChild(this.tabsScrollbar.getDomNode()); + // Tabs Container listeners + this.registerTabsContainerListeners(this.tabsContainer, this.tabsScrollbar); + // Editor Toolbar Container this.editorToolbarContainer = document.createElement('div'); addClass(this.editorToolbarContainer, 'editor-actions'); @@ -128,17 +132,11 @@ export class TabsTitleControl extends TitleControl { // Editor Actions Toolbar this.createEditorActionsToolBar(this.editorToolbarContainer); - // Close Action - this.closeOneEditorAction = this._register(this.instantiationService.createInstance(CloseOneEditorAction, CloseOneEditorAction.ID, CloseOneEditorAction.LABEL)); - // Breadcrumbs (are on a separate row below tabs and actions) const breadcrumbsContainer = document.createElement('div'); addClass(breadcrumbsContainer, 'tabs-breadcrumbs'); this.titleContainer.appendChild(breadcrumbsContainer); this.createBreadcrumbsControl(breadcrumbsContainer, { showFileIcons: true, showSymbolIcons: true, showDecorationColors: false, breadcrumbsBackground: breadcrumbsBackground }); - - // Tab Labels - this.tabResourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER)); } private createTabsScrollbar(scrollable: HTMLElement): ScrollableElement { @@ -170,23 +168,23 @@ export class TabsTitleControl extends TitleControl { this.group.relayout(); } - private registerTabsContainerListeners(): void { + private registerTabsContainerListeners(tabsContainer: HTMLElement, tabsScrollbar: ScrollableElement): void { // Group dragging - this.enableGroupDragging(this.tabsContainer); + this.enableGroupDragging(tabsContainer); // Forward scrolling inside the container to our custom scrollbar - this._register(addDisposableListener(this.tabsContainer, EventType.SCROLL, () => { - if (hasClass(this.tabsContainer, 'scroll')) { - this.tabsScrollbar.setScrollPosition({ - scrollLeft: this.tabsContainer.scrollLeft // during DND the container gets scrolled so we need to update the custom scrollbar + this._register(addDisposableListener(tabsContainer, EventType.SCROLL, () => { + if (hasClass(tabsContainer, 'scroll')) { + tabsScrollbar.setScrollPosition({ + scrollLeft: tabsContainer.scrollLeft // during DND the container gets scrolled so we need to update the custom scrollbar }); } })); // New file when double clicking on tabs container (but not tabs) - this._register(addDisposableListener(this.tabsContainer, EventType.DBLCLICK, e => { - if (e.target === this.tabsContainer) { + this._register(addDisposableListener(tabsContainer, EventType.DBLCLICK, e => { + if (e.target === tabsContainer) { EventHelper.stop(e); // {{SQL CARBON EDIT}} this.commandService.executeCommand(GlobalNewUntitledFileAction.ID).then(undefined, err => this.notificationService.warn(err)); @@ -194,29 +192,31 @@ export class TabsTitleControl extends TitleControl { })); // Prevent auto-scrolling (https://github.com/Microsoft/vscode/issues/16690) - this._register(addDisposableListener(this.tabsContainer, EventType.MOUSE_DOWN, (e: MouseEvent) => { + this._register(addDisposableListener(tabsContainer, EventType.MOUSE_DOWN, (e: MouseEvent) => { if (e.button === 1) { e.preventDefault(); } })); - // Drop support - this._register(new DragAndDropObserver(this.tabsContainer, { + this._register(new DragAndDropObserver(tabsContainer, { onDragEnter: e => { // Always enable support to scroll while dragging - addClass(this.tabsContainer, 'scroll'); + addClass(tabsContainer, 'scroll'); // Return if the target is not on the tabs container - if (e.target !== this.tabsContainer) { - this.updateDropFeedback(this.tabsContainer, false); // fixes https://github.com/Microsoft/vscode/issues/52093 + if (e.target !== tabsContainer) { + this.updateDropFeedback(tabsContainer, false); // fixes https://github.com/Microsoft/vscode/issues/52093 return; } // Return if transfer is unsupported if (!this.isSupportedDropTransfer(e)) { - e.dataTransfer!.dropEffect = 'none'; + if (e.dataTransfer) { + e.dataTransfer.dropEffect = 'none'; + } + return; } @@ -225,38 +225,46 @@ export class TabsTitleControl extends TitleControl { if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) { isLocalDragAndDrop = true; - const localDraggedEditor = this.editorTransfer.getData(DraggedEditorIdentifier.prototype)![0].identifier; - if (this.group.id === localDraggedEditor.groupId && this.group.getIndexOfEditor(localDraggedEditor.editor) === this.group.count - 1) { - e.dataTransfer!.dropEffect = 'none'; - return; + const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype); + if (Array.isArray(data)) { + const localDraggedEditor = data[0].identifier; + if (this.group.id === localDraggedEditor.groupId && this.group.getIndexOfEditor(localDraggedEditor.editor) === this.group.count - 1) { + if (e.dataTransfer) { + e.dataTransfer.dropEffect = 'none'; + } + + return; + } } } // Update the dropEffect to "copy" if there is no local data to be dragged because // in that case we can only copy the data into and not move it from its source if (!isLocalDragAndDrop) { - e.dataTransfer!.dropEffect = 'copy'; + if (e.dataTransfer) { + e.dataTransfer.dropEffect = 'copy'; + } } - this.updateDropFeedback(this.tabsContainer, true); + this.updateDropFeedback(tabsContainer, true); }, onDragLeave: e => { - this.updateDropFeedback(this.tabsContainer, false); - removeClass(this.tabsContainer, 'scroll'); + this.updateDropFeedback(tabsContainer, false); + removeClass(tabsContainer, 'scroll'); }, onDragEnd: e => { - this.updateDropFeedback(this.tabsContainer, false); - removeClass(this.tabsContainer, 'scroll'); + this.updateDropFeedback(tabsContainer, false); + removeClass(tabsContainer, 'scroll'); }, onDrop: e => { - this.updateDropFeedback(this.tabsContainer, false); - removeClass(this.tabsContainer, 'scroll'); + this.updateDropFeedback(tabsContainer, false); + removeClass(tabsContainer, 'scroll'); - if (e.target === this.tabsContainer) { - this.onDrop(e, this.group.count); + if (e.target === tabsContainer) { + this.onDrop(e, this.group.count, tabsContainer); } } })); @@ -273,8 +281,9 @@ export class TabsTitleControl extends TitleControl { openEditor(editor: IEditorInput): void { // Create tabs as needed - for (let i = this.tabsContainer.children.length; i < this.group.count; i++) { - this.tabsContainer.appendChild(this.createTab(i)); + const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar); + for (let i = tabsContainer.children.length; i < this.group.count; i++) { + tabsContainer.appendChild(this.createTab(i, tabsContainer, tabsScrollbar)); } // An add of a tab requires to recompute all labels @@ -305,10 +314,11 @@ export class TabsTitleControl extends TitleControl { if (this.group.activeEditor) { // Remove tabs that got closed - while (this.tabsContainer.children.length > this.group.count) { + const tabsContainer = assertIsDefined(this.tabsContainer); + while (tabsContainer.children.length > this.group.count) { // Remove one tab from container (must be the last to keep indexes in order!) - (this.tabsContainer.lastChild as HTMLElement).remove(); + (tabsContainer.lastChild as HTMLElement).remove(); // Remove associated tab label and widget this.tabDisposables.pop()!.dispose(); @@ -323,7 +333,9 @@ export class TabsTitleControl extends TitleControl { // No tabs to show else { - clearNode(this.tabsContainer); + if (this.tabsContainer) { + clearNode(this.tabsContainer); + } this.tabDisposables = dispose(this.tabDisposables); this.tabResourceLabels.clear(); @@ -419,13 +431,14 @@ export class TabsTitleControl extends TitleControl { private withTab(editor: IEditorInput, fn: (tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel) => void): void { const editorIndex = this.group.getIndexOfEditor(editor); - const tabContainer = this.tabsContainer.children[editorIndex] as HTMLElement; + const tabsContainer = assertIsDefined(this.tabsContainer); + const tabContainer = tabsContainer.children[editorIndex] as HTMLElement; if (tabContainer) { fn(tabContainer, this.tabResourceLabels.get(editorIndex), this.tabLabels[editorIndex]); } } - private createTab(index: number): HTMLElement { + private createTab(index: number, tabsContainer: HTMLElement, tabsScrollbar: ScrollableElement): HTMLElement { // Tab Container const tabContainer = document.createElement('div'); @@ -462,14 +475,14 @@ export class TabsTitleControl extends TitleControl { tabActionBar.onDidBeforeRun(() => this.blockRevealActiveTabOnce()); // Eventing - const eventsDisposable = this.registerTabListeners(tabContainer, index); + const eventsDisposable = this.registerTabListeners(tabContainer, index, tabsContainer, tabsScrollbar); this.tabDisposables.push(combinedDisposable(eventsDisposable, tabActionBar, tabActionRunner, editorLabel)); return tabContainer; } - private registerTabListeners(tab: HTMLElement, index: number): IDisposable { + private registerTabListeners(tab: HTMLElement, index: number, tabsContainer: HTMLElement, tabsScrollbar: ScrollableElement): IDisposable { const disposables = new DisposableStore(); const handleClickOrTouch = (e: MouseEvent | GestureEvent): void => { @@ -511,7 +524,7 @@ export class TabsTitleControl extends TitleControl { // Touch Scroll Support disposables.add(addDisposableListener(tab, TouchEventType.Change, (e: GestureEvent) => { - this.tabsScrollbar.setScrollPosition({ scrollLeft: this.tabsScrollbar.getScrollPosition().scrollLeft - e.translationX }); + tabsScrollbar.setScrollPosition({ scrollLeft: tabsScrollbar.getScrollPosition().scrollLeft - e.translationX }); })); // Close on mouse middle click @@ -572,7 +585,7 @@ export class TabsTitleControl extends TitleControl { if (target) { handled = true; this.group.openEditor(target, { preserveFocus: true }); - (this.tabsContainer.childNodes[targetIndex]).focus(); + (tabsContainer.childNodes[targetIndex]).focus(); } } @@ -581,8 +594,8 @@ export class TabsTitleControl extends TitleControl { } // moving in the tabs container can have an impact on scrolling position, so we need to update the custom scrollbar - this.tabsScrollbar.setScrollPosition({ - scrollLeft: this.tabsContainer.scrollLeft + tabsScrollbar.setScrollPosition({ + scrollLeft: tabsContainer.scrollLeft }); })); @@ -612,7 +625,9 @@ export class TabsTitleControl extends TitleControl { this.editorTransfer.setData([new DraggedEditorIdentifier({ editor, groupId: this.group.id })], DraggedEditorIdentifier.prototype); - e.dataTransfer!.effectAllowed = 'copyMove'; + if (e.dataTransfer) { + e.dataTransfer.effectAllowed = 'copyMove'; + } // Apply some datatransfer types to allow for dragging the element outside of the application const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); @@ -634,7 +649,10 @@ export class TabsTitleControl extends TitleControl { // Return if transfer is unsupported if (!this.isSupportedDropTransfer(e)) { - e.dataTransfer!.dropEffect = 'none'; + if (e.dataTransfer) { + e.dataTransfer.dropEffect = 'none'; + } + return; } @@ -643,17 +661,25 @@ export class TabsTitleControl extends TitleControl { if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) { isLocalDragAndDrop = true; - const localDraggedEditor = this.editorTransfer.getData(DraggedEditorIdentifier.prototype)![0].identifier; - if (localDraggedEditor.editor === this.group.getEditor(index) && localDraggedEditor.groupId === this.group.id) { - e.dataTransfer!.dropEffect = 'none'; - return; + const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype); + if (Array.isArray(data)) { + const localDraggedEditor = data[0].identifier; + if (localDraggedEditor.editor === this.group.getEditor(index) && localDraggedEditor.groupId === this.group.id) { + if (e.dataTransfer) { + e.dataTransfer.dropEffect = 'none'; + } + + return; + } } } // Update the dropEffect to "copy" if there is no local data to be dragged because // in that case we can only copy the data into and not move it from its source if (!isLocalDragAndDrop) { - e.dataTransfer!.dropEffect = 'copy'; + if (e.dataTransfer) { + e.dataTransfer.dropEffect = 'copy'; + } } this.updateDropFeedback(tab, true, index); @@ -675,7 +701,7 @@ export class TabsTitleControl extends TitleControl { removeClass(tab, 'dragged-over'); this.updateDropFeedback(tab, false, index); - this.onDrop(e, index); + this.onDrop(e, index, tabsContainer); } })); @@ -684,9 +710,12 @@ export class TabsTitleControl extends TitleControl { private isSupportedDropTransfer(e: DragEvent): boolean { if (this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype)) { - const group = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype)![0]; - if (group.identifier === this.group.id) { - return false; // groups cannot be dropped on title area it originates from + const data = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype); + if (Array.isArray(data)) { + const group = data[0]; + if (group.identifier === this.group.id) { + return false; // groups cannot be dropped on title area it originates from + } } return true; @@ -709,8 +738,8 @@ export class TabsTitleControl extends TitleControl { const isActiveTab = isTab && !!editor && this.group.isActive(editor); // Background - const noDNDBackgroundColor = isTab ? this.getColor(isActiveTab ? TAB_ACTIVE_BACKGROUND : TAB_INACTIVE_BACKGROUND) : null; - element.style.backgroundColor = isDND ? this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND) : noDNDBackgroundColor; + const noDNDBackgroundColor = isTab ? this.getColor(isActiveTab ? TAB_ACTIVE_BACKGROUND : TAB_INACTIVE_BACKGROUND) : ''; + element.style.backgroundColor = (isDND ? this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND) : noDNDBackgroundColor) || ''; // Outline const activeContrastBorderColor = this.getColor(activeContrastBorder); @@ -745,7 +774,7 @@ export class TabsTitleControl extends TitleControl { // Build labels and descriptions for each editor const labels = this.group.editors.map(editor => ({ editor, - name: editor.getName()!, + name: editor.getName(), description: editor.getDescription(verbosity), title: withNullAsUndefined(editor.getTitle(Verbosity.LONG)) })); @@ -856,7 +885,8 @@ export class TabsTitleControl extends TitleControl { private forEachTab(fn: (editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel) => void): void { this.group.editors.forEach((editor, index) => { - const tabContainer = this.tabsContainer.children[index] as HTMLElement; + const tabsContainer = assertIsDefined(this.tabsContainer); + const tabContainer = tabsContainer.children[index] as HTMLElement; if (tabContainer) { fn(editor, index, tabContainer, this.tabResourceLabels.get(index), this.tabLabels[index]); } @@ -870,7 +900,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.borderRight = borderRightColor ? `1px solid ${borderRightColor}` : ''; tabContainer.style.outlineColor = this.getColor(activeContrastBorder) || ''; // Settings @@ -929,7 +959,7 @@ export class TabsTitleControl extends TitleControl { // Container addClass(tabContainer, 'active'); tabContainer.setAttribute('aria-selected', 'true'); - tabContainer.style.backgroundColor = this.getColor(isGroupActive ? TAB_ACTIVE_BACKGROUND : TAB_UNFOCUSED_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) { @@ -959,8 +989,8 @@ export class TabsTitleControl extends TitleControl { // Container removeClass(tabContainer, 'active'); tabContainer.setAttribute('aria-selected', 'false'); - tabContainer.style.backgroundColor = this.getColor(TAB_INACTIVE_BACKGROUND); - tabContainer.style.boxShadow = null; + tabContainer.style.backgroundColor = this.getColor(TAB_INACTIVE_BACKGROUND) || ''; + tabContainer.style.boxShadow = ''; // Label tabLabelWidget.element.style.color = this.getColor(isGroupActive ? TAB_INACTIVE_FOREGROUND : TAB_UNFOCUSED_INACTIVE_FOREGROUND); @@ -1013,7 +1043,7 @@ export class TabsTitleControl extends TitleControl { return hasModifiedBorderColor; } - layout(dimension: Dimension): void { + layout(dimension: Dimension | undefined): void { this.dimension = dimension; const activeTab = this.group.activeEditor ? this.getTab(this.group.activeEditor) : undefined; @@ -1026,7 +1056,9 @@ export class TabsTitleControl extends TitleControl { // this a little bit we try at least to schedule this work on the next animation frame. if (!this.layoutScheduled.value) { this.layoutScheduled.value = scheduleAtNextAnimationFrame(() => { - this.doLayout(this.dimension); + const dimension = assertIsDefined(this.dimension); + this.doLayout(dimension); + this.layoutScheduled.clear(); }); } @@ -1038,16 +1070,18 @@ export class TabsTitleControl extends TitleControl { return; } + const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar); + if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) { this.breadcrumbsControl.layout({ width: dimension.width, height: BreadcrumbsControl.HEIGHT }); - this.tabsScrollbar.getDomNode().style.height = `${dimension.height - BreadcrumbsControl.HEIGHT}px`; + tabsScrollbar.getDomNode().style.height = `${dimension.height - BreadcrumbsControl.HEIGHT}px`; } - const visibleContainerWidth = this.tabsContainer.offsetWidth; - const totalContainerWidth = this.tabsContainer.scrollWidth; + const visibleContainerWidth = tabsContainer.offsetWidth; + const totalContainerWidth = tabsContainer.scrollWidth; - let activeTabPosX: number; - let activeTabWidth: number; + let activeTabPosX: number | undefined; + let activeTabWidth: number | undefined; if (!this.blockRevealActiveTab) { activeTabPosX = activeTab.offsetLeft; @@ -1055,33 +1089,33 @@ export class TabsTitleControl extends TitleControl { } // Update scrollbar - this.tabsScrollbar.setScrollDimensions({ + tabsScrollbar.setScrollDimensions({ width: visibleContainerWidth, scrollWidth: totalContainerWidth }); // Return now if we are blocked to reveal the active tab and clear flag - if (this.blockRevealActiveTab) { + if (this.blockRevealActiveTab || typeof activeTabPosX !== 'number' || typeof activeTabWidth !== 'number') { this.blockRevealActiveTab = false; return; } // Reveal the active one - const containerScrollPosX = this.tabsScrollbar.getScrollPosition().scrollLeft; - const activeTabFits = activeTabWidth! <= visibleContainerWidth; + const containerScrollPosX = tabsScrollbar.getScrollPosition().scrollLeft; + const activeTabFits = activeTabWidth <= visibleContainerWidth; // Tab is overflowing to the right: Scroll minimally until the element is fully visible to the right // Note: only try to do this if we actually have enough width to give to show the tab fully! - if (activeTabFits && containerScrollPosX + visibleContainerWidth < activeTabPosX! + activeTabWidth!) { - this.tabsScrollbar.setScrollPosition({ - scrollLeft: containerScrollPosX + ((activeTabPosX! + activeTabWidth!) /* right corner of tab */ - (containerScrollPosX + visibleContainerWidth) /* right corner of view port */) + if (activeTabFits && containerScrollPosX + visibleContainerWidth < activeTabPosX + activeTabWidth) { + tabsScrollbar.setScrollPosition({ + scrollLeft: containerScrollPosX + ((activeTabPosX + activeTabWidth) /* right corner of tab */ - (containerScrollPosX + visibleContainerWidth) /* right corner of view port */) }); } // Tab is overlflowng to the left or does not fit: Scroll it into view to the left - else if (containerScrollPosX > activeTabPosX! || !activeTabFits) { - this.tabsScrollbar.setScrollPosition({ - scrollLeft: activeTabPosX! + else if (containerScrollPosX > activeTabPosX || !activeTabFits) { + tabsScrollbar.setScrollPosition({ + scrollLeft: activeTabPosX }); } } @@ -1089,7 +1123,9 @@ export class TabsTitleControl extends TitleControl { private getTab(editor: IEditorInput): HTMLElement | undefined { const editorIndex = this.group.getIndexOfEditor(editor); if (editorIndex >= 0) { - return this.tabsContainer.children[editorIndex] as HTMLElement; + const tabsContainer = assertIsDefined(this.tabsContainer); + + return tabsContainer.children[editorIndex] as HTMLElement; } return undefined; @@ -1116,49 +1152,55 @@ export class TabsTitleControl extends TitleControl { return !!findParentWithClass(element, 'action-item', 'tab'); } - private onDrop(e: DragEvent, targetIndex: number): void { + private onDrop(e: DragEvent, targetIndex: number, tabsContainer: HTMLElement): void { EventHelper.stop(e, true); - this.updateDropFeedback(this.tabsContainer, false); - removeClass(this.tabsContainer, 'scroll'); + this.updateDropFeedback(tabsContainer, false); + removeClass(tabsContainer, 'scroll'); // Local Editor DND if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) { - const draggedEditor = this.editorTransfer.getData(DraggedEditorIdentifier.prototype)![0].identifier; - const sourceGroup = this.accessor.getGroup(draggedEditor.groupId); + const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype); + if (Array.isArray(data)) { + const draggedEditor = data[0].identifier; + const sourceGroup = this.accessor.getGroup(draggedEditor.groupId); - if (sourceGroup) { + if (sourceGroup) { - // Move editor to target position and index - if (this.isMoveOperation(e, draggedEditor.groupId)) { - sourceGroup.moveEditor(draggedEditor.editor, this.group, { index: targetIndex }); + // Move editor to target position and index + if (this.isMoveOperation(e, draggedEditor.groupId)) { + sourceGroup.moveEditor(draggedEditor.editor, this.group, { index: targetIndex }); + } + + // Copy editor to target position and index + else { + sourceGroup.copyEditor(draggedEditor.editor, this.group, { index: targetIndex }); + } } - // Copy editor to target position and index - else { - sourceGroup.copyEditor(draggedEditor.editor, this.group, { index: targetIndex }); - } + this.group.focus(); + this.editorTransfer.clearData(DraggedEditorIdentifier.prototype); } - - this.group.focus(); - this.editorTransfer.clearData(DraggedEditorIdentifier.prototype); } // Local Editor Group DND else if (this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype)) { - const sourceGroup = this.accessor.getGroup(this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype)![0].identifier); + const data = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype); + if (data) { + const sourceGroup = this.accessor.getGroup(data[0].identifier); - if (sourceGroup) { - const mergeGroupOptions: IMergeGroupOptions = { index: targetIndex }; - if (!this.isMoveOperation(e, sourceGroup.id)) { - mergeGroupOptions.mode = MergeGroupMode.COPY_EDITORS; + if (sourceGroup) { + const mergeGroupOptions: IMergeGroupOptions = { index: targetIndex }; + if (!this.isMoveOperation(e, sourceGroup.id)) { + mergeGroupOptions.mode = MergeGroupMode.COPY_EDITORS; + } + + this.accessor.mergeGroup(sourceGroup, this.group, mergeGroupOptions); } - this.accessor.mergeGroup(sourceGroup, this.group, mergeGroupOptions); + this.group.focus(); + this.groupTransfer.clearData(DraggedEditorGroupIdentifier.prototype); } - - this.group.focus(); - this.groupTransfer.clearData(DraggedEditorGroupIdentifier.prototype); } // External DND diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 7e11fe486b..4a9a80598a 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import * as objects from 'vs/base/common/objects'; -import { isFunction, isObject, isArray } from 'vs/base/common/types'; +import { isFunction, isObject, isArray, assertIsDefined } from 'vs/base/common/types'; import { IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditorOptions, IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor'; @@ -110,7 +110,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { } // Set Editor Model - const diffEditor = this.getControl(); + const diffEditor = assertIsDefined(this.getControl()); const resolvedDiffEditorModel = resolvedModel; diffEditor.setModel(resolvedDiffEditorModel.textDiffEditorModel); @@ -123,7 +123,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { // Otherwise restore View State let hasPreviousViewState = false; if (!optionsGotApplied) { - hasPreviousViewState = this.restoreTextDiffEditorViewState(input); + hasPreviousViewState = this.restoreTextDiffEditorViewState(input, diffEditor); } // Diff navigator @@ -148,17 +148,18 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { setOptions(options: EditorOptions | undefined): void { const textOptions = options; if (textOptions && isFunction(textOptions.apply)) { - textOptions.apply(this.getControl(), ScrollType.Smooth); + const diffEditor = assertIsDefined(this.getControl()); + textOptions.apply(diffEditor, ScrollType.Smooth); } } - private restoreTextDiffEditorViewState(input: EditorInput): boolean { - if (input instanceof DiffEditorInput) { - const resource = this.toDiffEditorViewStateResource(input); + private restoreTextDiffEditorViewState(editor: EditorInput, control: IDiffEditor): boolean { + if (editor instanceof DiffEditorInput) { + const resource = this.toDiffEditorViewStateResource(editor); if (resource) { const viewState = this.loadTextEditorViewState(resource); if (viewState) { - this.getControl().restoreViewState(viewState); + control.restoreViewState(viewState); return true; } @@ -268,7 +269,10 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { this.saveTextDiffEditorViewState(this.input); // Clear Model - this.getControl().setModel(null); + const diffEditor = this.getControl(); + if (diffEditor) { + diffEditor.setModel(null); + } // Pass to super super.clearInput(); @@ -278,8 +282,8 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { return this.diffNavigator; } - getControl(): IDiffEditor { - return super.getControl() as IDiffEditor; + getControl(): IDiffEditor | undefined { + return super.getControl() as IDiffEditor | undefined; } protected loadTextEditorViewState(resource: URI): IDiffEditorViewState { @@ -317,7 +321,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { } private retrieveTextDiffEditorViewState(resource: URI): IDiffEditorViewState | null { - const control = this.getControl(); + const control = assertIsDefined(this.getControl()); const model = control.getModel(); if (!model || !model.modified || !model.original) { return null; // view state always needs a model diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index 1e50b79c73..240e97a981 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -3,11 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import * as objects from 'vs/base/common/objects'; -import * as types from 'vs/base/common/types'; -import * as DOM from 'vs/base/browser/dom'; +import { distinct, deepClone, assign } from 'vs/base/common/objects'; +import { isObject, assertIsDefined } from 'vs/base/common/types'; +import { Dimension } from 'vs/base/browser/dom'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { EditorInput, EditorOptions, IEditorMemento, ITextEditor } from 'vs/workbench/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; @@ -37,8 +37,8 @@ export interface IEditorConfiguration { * be subclassed and not instantiated. */ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { - private editorControl: IEditor; - private _editorContainer: HTMLElement; + private editorControl: IEditor | undefined; + private _editorContainer: HTMLElement | undefined; private hasPendingConfigurationChange: boolean | undefined; private lastAppliedEditorOptions?: IEditorOptions; private editorMemento: IEditorMemento; @@ -96,8 +96,8 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { protected computeConfiguration(configuration: IEditorConfiguration): IEditorOptions { // Specific editor options always overwrite user configuration - const editorConfiguration: IEditorOptions = types.isObject(configuration.editor) ? objects.deepClone(configuration.editor) : Object.create(null); - objects.assign(editorConfiguration, this.getConfigurationOverrides()); + const editorConfiguration: IEditorOptions = isObject(configuration.editor) ? deepClone(configuration.editor) : Object.create(null); + assign(editorConfiguration, this.getConfigurationOverrides()); // ARIA label editorConfiguration.ariaLabel = this.computeAriaLabel(); @@ -111,7 +111,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { // Apply group information to help identify in which group we are if (ariaLabel) { if (this.group) { - ariaLabel = nls.localize('editorLabelWithGroup', "{0}, {1}.", ariaLabel, this.group.label); + ariaLabel = localize('editorLabelWithGroup', "{0}, {1}.", ariaLabel, this.group.label); } } @@ -120,7 +120,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { protected getConfigurationOverrides(): IEditorOptions { const overrides = {}; - objects.assign(overrides, { + assign(overrides, { overviewRulerLanes: 3, lineNumbersMinChars: 3, fixedOverflowWidgets: true @@ -133,7 +133,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { // Editor for Text this._editorContainer = parent; - this.editorControl = this._register(this.createEditorControl(parent, this.computeConfiguration(this.configurationService.getValue(this.getResource()!)))); + this.editorControl = this._register(this.createEditorControl(parent, this.computeConfiguration(this.configurationService.getValue(this.getResource())))); // Model & Language changes const codeEditor = getCodeEditor(this.editorControl); @@ -197,33 +197,40 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { // Update editor options after having set the input. We do this because there can be // editor input specific options (e.g. an ARIA label depending on the input showing) this.updateEditorConfiguration(); - this._editorContainer.setAttribute('aria-label', this.computeAriaLabel()); + + const editorContainer = assertIsDefined(this._editorContainer); + editorContainer.setAttribute('aria-label', this.computeAriaLabel()); } protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { // Pass on to Editor + const editorControl = assertIsDefined(this.editorControl); if (visible) { this.consumePendingConfigurationChangeEvent(); - this.editorControl.onVisible(); + editorControl.onVisible(); } else { - this.editorControl.onHide(); + editorControl.onHide(); } super.setEditorVisible(visible, group); } focus(): void { - this.editorControl.focus(); - } - - layout(dimension: DOM.Dimension): void { // Pass on to Editor - this.editorControl.layout(dimension); + const editorControl = assertIsDefined(this.editorControl); + editorControl.focus(); } - getControl(): IEditor { + layout(dimension: Dimension): void { + + // Pass on to Editor + const editorControl = assertIsDefined(this.editorControl); + editorControl.layout(dimension); + } + + getControl(): IEditor | undefined { return this.editorControl; } @@ -296,7 +303,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { // have been applied to the editor directly. let editorSettingsToApply = editorConfiguration; if (this.lastAppliedEditorOptions) { - editorSettingsToApply = objects.distinct(this.lastAppliedEditorOptions, editorSettingsToApply); + editorSettingsToApply = distinct(this.lastAppliedEditorOptions, editorSettingsToApply); } if (Object.keys(editorSettingsToApply).length > 0) { diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 6c38284089..19f392d354 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as types from 'vs/base/common/types'; +import { assertIsDefined, isFunction } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { TextEditorOptions, EditorInput, EditorOptions } from 'vs/workbench/common/editor'; @@ -19,7 +19,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { Event } from 'vs/base/common/event'; -import { ScrollType } from 'vs/editor/common/editorCommon'; +import { ScrollType, IEditor } from 'vs/editor/common/editorCommon'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -74,36 +74,37 @@ export class AbstractTextResourceEditor extends BaseTextEditor { } // Set Editor Model - const textEditor = this.getControl(); + const textEditor = assertIsDefined(this.getControl()); const textEditorModel = resolvedModel.textEditorModel; textEditor.setModel(textEditorModel); // Apply Options from TextOptions let optionsGotApplied = false; const textOptions = options; - if (textOptions && types.isFunction(textOptions.apply)) { + if (textOptions && isFunction(textOptions.apply)) { optionsGotApplied = textOptions.apply(textEditor, ScrollType.Immediate); } // Otherwise restore View State if (!optionsGotApplied) { - this.restoreTextResourceEditorViewState(input); + this.restoreTextResourceEditorViewState(input, textEditor); } } - private restoreTextResourceEditorViewState(input: EditorInput) { - if (input instanceof UntitledEditorInput || input instanceof ResourceEditorInput) { - const viewState = this.loadTextEditorViewState(input.getResource()); + private restoreTextResourceEditorViewState(editor: EditorInput, control: IEditor) { + if (editor instanceof UntitledEditorInput || editor instanceof ResourceEditorInput) { + const viewState = this.loadTextEditorViewState(editor.getResource()); if (viewState) { - this.getControl().restoreViewState(viewState); + control.restoreViewState(viewState); } } } setOptions(options: EditorOptions | undefined): void { const textOptions = options; - if (textOptions && types.isFunction(textOptions.apply)) { - textOptions.apply(this.getControl(), ScrollType.Smooth); + if (textOptions && isFunction(textOptions.apply)) { + const textEditor = assertIsDefined(this.getControl()); + textOptions.apply(textEditor, ScrollType.Smooth); } } @@ -120,7 +121,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor { const isReadonly = !(this.input instanceof UntitledEditorInput); let ariaLabel: string; - const inputName = input && input.getName(); + const inputName = input?.getName(); if (isReadonly) { ariaLabel = inputName ? nls.localize('readonlyEditorWithInputAriaLabel', "{0}. Readonly text editor.", inputName) : nls.localize('readonlyEditorAriaLabel', "Readonly text editor."); } else { @@ -149,7 +150,10 @@ export class AbstractTextResourceEditor extends BaseTextEditor { this.saveTextResourceEditorViewState(this.input); // Clear Model - this.getControl().setModel(null); + const textEditor = this.getControl(); + if (textEditor) { + textEditor.setModel(null); + } super.clearInput(); } diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index a72d7c7262..d980297e5f 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -39,7 +39,7 @@ import { Themable } from 'vs/workbench/common/theme'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IFileService } from 'vs/platform/files/common/files'; -import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; +import { withNullAsUndefined, withUndefinedAsNull, assertIsDefined } from 'vs/base/common/types'; import { ILabelService } from 'vs/platform/label/common/label'; export interface IToolbarActions { @@ -57,7 +57,7 @@ export abstract class TitleControl extends Themable { private currentPrimaryEditorActionIds: string[] = []; private currentSecondaryEditorActionIds: string[] = []; - private editorActionsToolbar: ToolBar; + private editorActionsToolbar: ToolBar | undefined; private resourceContext: ResourceContextKey; private editorPinnedContext: IContextKey; @@ -189,7 +189,8 @@ export abstract class TitleControl extends Themable { primaryEditorActions.some(action => action instanceof ExecuteCommandAction) || // execute command actions can have the same ID but different arguments secondaryEditorActions.some(action => action instanceof ExecuteCommandAction) // see also https://github.com/Microsoft/vscode/issues/16298 ) { - this.editorActionsToolbar.setActions(primaryEditorActions, secondaryEditorActions)(); + const editorActionsToolbar = assertIsDefined(this.editorActionsToolbar); + editorActionsToolbar.setActions(primaryEditorActions, secondaryEditorActions)(); this.currentPrimaryEditorActionIds = primaryEditorActionIds; this.currentSecondaryEditorActionIds = secondaryEditorActionIds; @@ -228,7 +229,7 @@ export abstract class TitleControl extends Themable { const activeControl = this.group.activeControl; if (activeControl instanceof BaseEditor) { const codeEditor = getCodeEditor(activeControl.getControl()); - const scopedContextKeyService = codeEditor && codeEditor.invokeWithinContext(accessor => accessor.get(IContextKeyService)) || this.contextKeyService; + const scopedContextKeyService = codeEditor?.invokeWithinContext(accessor => accessor.get(IContextKeyService)) || this.contextKeyService; const titleBarMenu = this.menuService.createMenu(MenuId.EditorTitle, scopedContextKeyService); this.editorToolBarMenuDisposables.add(titleBarMenu); this.editorToolBarMenuDisposables.add(titleBarMenu.onDidChange(() => { @@ -242,7 +243,9 @@ export abstract class TitleControl extends Themable { } protected clearEditorActionsToolbar(): void { - this.editorActionsToolbar.setActions([], [])(); + if (this.editorActionsToolbar) { + this.editorActionsToolbar.setActions([], [])(); + } this.currentPrimaryEditorActionIds = []; this.currentSecondaryEditorActionIds = []; @@ -258,7 +261,9 @@ export abstract class TitleControl extends Themable { // Set editor group as transfer this.groupTransfer.setData([new DraggedEditorGroupIdentifier(this.group.id)], DraggedEditorGroupIdentifier.prototype); - e.dataTransfer!.effectAllowed = 'copyMove'; + if (e.dataTransfer) { + e.dataTransfer.effectAllowed = 'copyMove'; + } // If tabs are disabled, treat dragging as if an editor tab was dragged if (!this.accessor.partOptions.showTabs) { diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsList.css b/src/vs/workbench/browser/parts/notifications/media/notificationsList.css index 702b1b0f6f..ca8807406f 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsList.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsList.css @@ -38,6 +38,7 @@ height: 22px; margin-right: 4px; margin-left: 4px; + font-size: 18px; background-position: center; background-repeat: no-repeat; } @@ -70,8 +71,7 @@ } .monaco-workbench .notifications-list-container .notification-list-item:hover .notification-list-item-toolbar-container, -.monaco-workbench .notifications-list-container .monaco-list-row.focused .notification-list-item .notification-list-item-toolbar-container, -.monaco-workbench .notifications-list-container .notification-list-item.expanded .notification-list-item-toolbar-container { +.monaco-workbench .notifications-list-container .monaco-list-row.focused .notification-list-item .notification-list-item-toolbar-container { display: block; } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts index bae33812c5..062f128bbb 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts @@ -43,7 +43,7 @@ export class ClearAllNotificationsAction extends Action { label: string, @ICommandService private readonly commandService: ICommandService ) { - super(id, label, 'codicon-close-all'); + super(id, label, 'codicon-clear-all'); } run(notification: INotificationViewItem): Promise { @@ -63,7 +63,7 @@ export class HideNotificationsCenterAction extends Action { label: string, @ICommandService private readonly commandService: ICommandService ) { - super(id, label, 'codicon-chevron-down'); + super(id, label, 'codicon-close'); } run(notification: INotificationViewItem): Promise { diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index 34978275d4..14d4c84f2b 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -22,21 +22,23 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { ClearAllNotificationsAction, HideNotificationsCenterAction, NotificationActionRunner } from 'vs/workbench/browser/parts/notifications/notificationsActions'; import { IAction } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { assertAllDefined, assertIsDefined } from 'vs/base/common/types'; export class NotificationsCenter extends Themable { - private static MAX_DIMENSIONS = new Dimension(450, 400); + private static readonly MAX_DIMENSIONS = new Dimension(450, 400); private readonly _onDidChangeVisibility: Emitter = this._register(new Emitter()); readonly onDidChangeVisibility: Event = this._onDidChangeVisibility.event; - private notificationsCenterContainer: HTMLElement; - private notificationsCenterHeader: HTMLElement; - private notificationsCenterTitle: HTMLSpanElement; - private notificationsList: NotificationsList; - private _isVisible: boolean; - private workbenchDimensions: Dimension; + private notificationsCenterContainer: HTMLElement | undefined; + private notificationsCenterHeader: HTMLElement | undefined; + private notificationsCenterTitle: HTMLSpanElement | undefined; + private notificationsList: NotificationsList | undefined; + private _isVisible: boolean | undefined; + private workbenchDimensions: Dimension | undefined; private notificationsCenterVisibleContextKey: IContextKey; + private clearAllAction: ClearAllNotificationsAction | undefined; constructor( private container: HTMLElement, @@ -61,12 +63,13 @@ export class NotificationsCenter extends Themable { } get isVisible(): boolean { - return this._isVisible; + return !!this._isVisible; } show(): void { if (this._isVisible) { - this.notificationsList.show(true /* focus */); + const notificationsList = assertIsDefined(this.notificationsList); + notificationsList.show(true /* focus */); return; // already visible } @@ -80,18 +83,19 @@ export class NotificationsCenter extends Themable { this.updateTitle(); // Make visible + const [notificationsList, notificationsCenterContainer] = assertAllDefined(this.notificationsList, this.notificationsCenterContainer); this._isVisible = true; - addClass(this.notificationsCenterContainer, 'visible'); - this.notificationsList.show(); + addClass(notificationsCenterContainer, 'visible'); + notificationsList.show(); // Layout this.layout(this.workbenchDimensions); // Show all notifications that are present now - this.notificationsList.updateNotificationsList(0, 0, this.model.notifications); + notificationsList.updateNotificationsList(0, 0, this.model.notifications); // Focus first - this.notificationsList.focusFirst(); + notificationsList.focusFirst(); // Theming this.updateStyles(); @@ -104,10 +108,14 @@ export class NotificationsCenter extends Themable { } private updateTitle(): void { + const [notificationsCenterTitle, clearAllAction] = assertAllDefined(this.notificationsCenterTitle, this.clearAllAction); + if (this.model.notifications.length === 0) { - this.notificationsCenterTitle.textContent = localize('notificationsEmpty', "No new notifications"); + notificationsCenterTitle.textContent = localize('notificationsEmpty', "No new notifications"); + clearAllAction.enabled = false; } else { - this.notificationsCenterTitle.textContent = localize('notifications', "Notifications"); + notificationsCenterTitle.textContent = localize('notifications', "Notifications"); + clearAllAction.enabled = true; } } @@ -139,12 +147,12 @@ export class NotificationsCenter extends Themable { actionRunner })); + this.clearAllAction = this._register(this.instantiationService.createInstance(ClearAllNotificationsAction, ClearAllNotificationsAction.ID, ClearAllNotificationsAction.LABEL)); + notificationsToolBar.push(this.clearAllAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(this.clearAllAction) }); + const hideAllAction = this._register(this.instantiationService.createInstance(HideNotificationsCenterAction, HideNotificationsCenterAction.ID, HideNotificationsCenterAction.LABEL)); notificationsToolBar.push(hideAllAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(hideAllAction) }); - const clearAllAction = this._register(this.instantiationService.createInstance(ClearAllNotificationsAction, ClearAllNotificationsAction.ID, ClearAllNotificationsAction.LABEL)); - notificationsToolBar.push(clearAllAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(clearAllAction) }); - // Notifications List this.notificationsList = this.instantiationService.createInstance(NotificationsList, this.notificationsCenterContainer, { ariaLabel: localize('notificationsList', "Notifications List") @@ -167,16 +175,17 @@ export class NotificationsCenter extends Themable { let focusGroup = false; // Update notifications list based on event + const [notificationsList, notificationsCenterContainer] = assertAllDefined(this.notificationsList, this.notificationsCenterContainer); switch (e.kind) { case NotificationChangeType.ADD: - this.notificationsList.updateNotificationsList(e.index, 0, [e.item]); + notificationsList.updateNotificationsList(e.index, 0, [e.item]); break; case NotificationChangeType.CHANGE: - this.notificationsList.updateNotificationsList(e.index, 1, [e.item]); + notificationsList.updateNotificationsList(e.index, 1, [e.item]); break; case NotificationChangeType.REMOVE: - focusGroup = isAncestor(document.activeElement, this.notificationsCenterContainer); - this.notificationsList.updateNotificationsList(e.index, 1); + focusGroup = isAncestor(document.activeElement, notificationsCenterContainer); + notificationsList.updateNotificationsList(e.index, 1); break; } @@ -195,7 +204,7 @@ export class NotificationsCenter extends Themable { } hide(): void { - if (!this._isVisible || !this.notificationsCenterContainer) { + if (!this._isVisible || !this.notificationsCenterContainer || !this.notificationsList) { return; // already hidden } @@ -219,22 +228,22 @@ export class NotificationsCenter extends Themable { } protected updateStyles(): void { - if (this.notificationsCenterContainer) { + if (this.notificationsCenterContainer && this.notificationsCenterHeader) { const widgetShadowColor = this.getColor(widgetShadow); - this.notificationsCenterContainer.style.boxShadow = widgetShadowColor ? `0 0px 8px ${widgetShadowColor}` : null; + this.notificationsCenterContainer.style.boxShadow = widgetShadowColor ? `0 0px 8px ${widgetShadowColor}` : ''; const borderColor = this.getColor(NOTIFICATIONS_CENTER_BORDER); - this.notificationsCenterContainer.style.border = borderColor ? `1px solid ${borderColor}` : null; + this.notificationsCenterContainer.style.border = borderColor ? `1px solid ${borderColor}` : ''; const headerForeground = this.getColor(NOTIFICATIONS_CENTER_HEADER_FOREGROUND); this.notificationsCenterHeader.style.color = headerForeground ? headerForeground.toString() : null; const headerBackground = this.getColor(NOTIFICATIONS_CENTER_HEADER_BACKGROUND); - this.notificationsCenterHeader.style.background = headerBackground ? headerBackground.toString() : null; + this.notificationsCenterHeader.style.background = headerBackground ? headerBackground.toString() : ''; } } - layout(dimension: Dimension): void { + layout(dimension: Dimension | undefined): void { this.workbenchDimensions = dimension; if (this._isVisible && this.notificationsCenterContainer) { @@ -264,7 +273,8 @@ export class NotificationsCenter extends Themable { } // Apply to list - this.notificationsList.layout(Math.min(maxWidth, availableWidth), Math.min(maxHeight, availableHeight)); + const notificationsList = assertIsDefined(this.notificationsList); + notificationsList.layout(Math.min(maxWidth, availableWidth), Math.min(maxHeight, availableHeight)); } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsList.ts b/src/vs/workbench/browser/parts/notifications/notificationsList.ts index 7f3fc322d4..c3c2b09344 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsList.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsList.ts @@ -16,10 +16,11 @@ import { NotificationsListDelegate, NotificationRenderer } from 'vs/workbench/br import { NotificationActionRunner, CopyNotificationMessageAction } from 'vs/workbench/browser/parts/notifications/notificationsActions'; import { NotificationFocusedContext } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; export class NotificationsList extends Themable { - private listContainer: HTMLElement; - private list: WorkbenchList; + private listContainer: HTMLElement | undefined; + private list: WorkbenchList | undefined; private viewModel: INotificationViewItem[]; private isVisible: boolean | undefined; @@ -38,7 +39,8 @@ export class NotificationsList extends Themable { show(focus?: boolean): void { if (this.isVisible) { if (focus) { - this.list.domFocus(); + const list = assertIsDefined(this.list); + list.domFocus(); } return; // already visible @@ -54,7 +56,8 @@ export class NotificationsList extends Themable { // Focus if (focus) { - this.list.domFocus(); + const list = assertIsDefined(this.list); + list.domFocus(); } } @@ -70,7 +73,7 @@ export class NotificationsList extends Themable { const renderer = this.instantiationService.createInstance(NotificationRenderer, actionRunner); // List - this.list = this._register(this.instantiationService.createInstance( + const list = this.list = this._register(this.instantiationService.createInstance( WorkbenchList, 'NotificationsList', this.listContainer, @@ -85,13 +88,13 @@ export class NotificationsList extends Themable { // Context menu to copy message const copyAction = this._register(this.instantiationService.createInstance(CopyNotificationMessageAction, CopyNotificationMessageAction.ID, CopyNotificationMessageAction.LABEL)); - this._register((this.list.onContextMenu(e => { + this._register((list.onContextMenu(e => { if (!e.element) { return; } this.contextMenuService.showContextMenu({ - getAnchor: () => e.anchor!, + getAnchor: () => e.anchor, getActions: () => [copyAction], getActionsContext: () => e.element, actionRunner @@ -99,27 +102,27 @@ export class NotificationsList extends Themable { }))); // Toggle on double click - this._register((this.list.onMouseDblClick(event => (event.element as INotificationViewItem).toggle()))); + this._register((list.onMouseDblClick(event => (event.element as INotificationViewItem).toggle()))); // Clear focus when DOM focus moves out // Use document.hasFocus() to not clear the focus when the entire window lost focus // This ensures that when the focus comes back, the notification is still focused - const listFocusTracker = this._register(trackFocus(this.list.getHTMLElement())); + const listFocusTracker = this._register(trackFocus(list.getHTMLElement())); this._register(listFocusTracker.onDidBlur(() => { if (document.hasFocus()) { - this.list.setFocus([]); + list.setFocus([]); } })); // Context key - NotificationFocusedContext.bindTo(this.list.contextKeyService); + NotificationFocusedContext.bindTo(list.contextKeyService); // Only allow for focus in notifications, as the // selection is too strong over the contents of // the notification - this._register(this.list.onSelectionChange(e => { + this._register(list.onSelectionChange(e => { if (e.indexes.length > 0) { - this.list.setSelection([]); + list.setSelection([]); } })); @@ -129,23 +132,24 @@ export class NotificationsList extends Themable { } updateNotificationsList(start: number, deleteCount: number, items: INotificationViewItem[] = []) { - const listHasDOMFocus = isAncestor(document.activeElement, this.listContainer); + const [list, listContainer] = assertAllDefined(this.list, this.listContainer); + const listHasDOMFocus = isAncestor(document.activeElement, listContainer); // Remember focus and relative top of that item - const focusedIndex = this.list.getFocus()[0]; + const focusedIndex = list.getFocus()[0]; const focusedItem = this.viewModel[focusedIndex]; let focusRelativeTop: number | null = null; if (typeof focusedIndex === 'number') { - focusRelativeTop = this.list.getRelativeTop(focusedIndex); + focusRelativeTop = list.getRelativeTop(focusedIndex); } // Update view model this.viewModel.splice(start, deleteCount, ...items); // Update list - this.list.splice(start, deleteCount, items); - this.list.layout(); + list.splice(start, deleteCount, items); + list.layout(); // Hide if no more notifications to show if (this.viewModel.length === 0) { @@ -167,15 +171,15 @@ export class NotificationsList extends Themable { } if (typeof focusRelativeTop === 'number') { - this.list.reveal(indexToFocus, focusRelativeTop); + list.reveal(indexToFocus, focusRelativeTop); } - this.list.setFocus([indexToFocus]); + list.setFocus([indexToFocus]); } // Restore DOM focus if we had focus before if (listHasDOMFocus) { - this.list.domFocus(); + list.domFocus(); } } @@ -204,7 +208,7 @@ export class NotificationsList extends Themable { } hasFocus(): boolean { - if (!this.isVisible || !this.list) { + if (!this.isVisible || !this.listContainer) { return false; // hidden } @@ -217,7 +221,7 @@ export class NotificationsList extends Themable { this.listContainer.style.color = foreground ? foreground.toString() : null; const background = this.getColor(NOTIFICATIONS_BACKGROUND); - this.listContainer.style.background = background ? background.toString() : null; + this.listContainer.style.background = background ? background.toString() : ''; const outlineColor = this.getColor(contrastBorder); this.listContainer.style.outlineColor = outlineColor ? outlineColor.toString() : ''; @@ -225,7 +229,7 @@ export class NotificationsList extends Themable { } layout(width: number, maxHeight?: number): void { - if (this.list) { + if (this.listContainer && this.list) { this.listContainer.style.width = `${width}px`; if (typeof maxHeight === 'number') { diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index 044151365c..f43e42ddfb 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -18,11 +18,12 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { NotificationsToastsVisibleContext } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { localize } from 'vs/nls'; -import { Severity } from 'vs/platform/notification/common/notification'; +import { Severity, NotificationsFilter } from 'vs/platform/notification/common/notification'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { timeout } from 'vs/base/common/async'; +import { assertIsDefined } from 'vs/base/common/types'; interface INotificationToast { item: INotificationViewItem; @@ -40,8 +41,8 @@ enum ToastVisibility { export class NotificationsToasts extends Themable { - private static MAX_WIDTH = 450; - private static MAX_NOTIFICATIONS = 3; + private static readonly MAX_WIDTH = 450; + private static readonly MAX_NOTIFICATIONS = 3; private static PURGE_TIMEOUT: { [severity: number]: number } = (() => { const intervals = Object.create(null); @@ -52,8 +53,8 @@ export class NotificationsToasts extends Themable { return intervals; })(); - private notificationsToastsContainer: HTMLElement; - private workbenchDimensions: Dimension; + private notificationsToastsContainer: HTMLElement | undefined; + private workbenchDimensions: Dimension | undefined; private isNotificationsCenterVisible: boolean | undefined; private mapNotificationToToast: Map; private notificationsToastsVisibleContextKey: IContextKey; @@ -91,6 +92,13 @@ export class NotificationsToasts extends Themable { // Update toasts on notification changes this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e))); }); + + // Filter + this._register(this.model.onDidFilterChange(filter => { + if (filter === NotificationsFilter.SILENT || filter === NotificationsFilter.ERROR) { + this.hide(); + } + })); } private async onCanShowNotifications(): Promise { @@ -125,15 +133,16 @@ export class NotificationsToasts extends Themable { } // Lazily create toasts containers - if (!this.notificationsToastsContainer) { - this.notificationsToastsContainer = document.createElement('div'); - addClass(this.notificationsToastsContainer, 'notifications-toasts'); + let notificationsToastsContainer = this.notificationsToastsContainer; + if (!notificationsToastsContainer) { + notificationsToastsContainer = this.notificationsToastsContainer = document.createElement('div'); + addClass(notificationsToastsContainer, 'notifications-toasts'); - this.container.appendChild(this.notificationsToastsContainer); + this.container.appendChild(notificationsToastsContainer); } // Make Visible - addClass(this.notificationsToastsContainer, 'visible'); + addClass(notificationsToastsContainer, 'visible'); const itemDisposables = new DisposableStore(); @@ -141,11 +150,11 @@ export class NotificationsToasts extends Themable { const notificationToastContainer = document.createElement('div'); addClass(notificationToastContainer, 'notification-toast-container'); - const firstToast = this.notificationsToastsContainer.firstChild; + const firstToast = notificationsToastsContainer.firstChild; if (firstToast) { - this.notificationsToastsContainer.insertBefore(notificationToastContainer, firstToast); // always first + notificationsToastsContainer.insertBefore(notificationToastContainer, firstToast); // always first } else { - this.notificationsToastsContainer.appendChild(notificationToastContainer); + notificationsToastsContainer.appendChild(notificationToastContainer); } // Toast @@ -155,6 +164,7 @@ export class NotificationsToasts extends Themable { // Create toast with item and show const notificationList = this.instantiationService.createInstance(NotificationsList, notificationToast, { + ariaRole: 'dialog', // https://github.com/microsoft/vscode/issues/82728 ariaLabel: localize('notificationsToast', "Notification Toast"), verticalScrollMode: ScrollbarVisibility.Hidden }); @@ -164,8 +174,8 @@ export class NotificationsToasts extends Themable { this.mapNotificationToToast.set(item, toast); itemDisposables.add(toDisposable(() => { - if (this.isVisible(toast)) { - this.notificationsToastsContainer.removeChild(toast.container); + if (this.isVisible(toast) && notificationsToastsContainer) { + notificationsToastsContainer.removeChild(toast.container); } })); @@ -318,7 +328,7 @@ export class NotificationsToasts extends Themable { } hide(): void { - const focusGroup = isAncestor(document.activeElement, this.notificationsToastsContainer); + const focusGroup = this.notificationsToastsContainer ? isAncestor(document.activeElement, this.notificationsToastsContainer) : false; this.removeToasts(); @@ -412,10 +422,10 @@ export class NotificationsToasts extends Themable { protected updateStyles(): void { this.mapNotificationToToast.forEach(t => { const widgetShadowColor = this.getColor(widgetShadow); - t.toast.style.boxShadow = widgetShadowColor ? `0 0px 8px ${widgetShadowColor}` : null; + t.toast.style.boxShadow = widgetShadowColor ? `0 0px 8px ${widgetShadowColor}` : ''; const borderColor = this.getColor(NOTIFICATIONS_TOAST_BORDER); - t.toast.style.border = borderColor ? `1px solid ${borderColor}` : null; + t.toast.style.border = borderColor ? `1px solid ${borderColor}` : ''; }); } @@ -443,7 +453,7 @@ export class NotificationsToasts extends Themable { return notificationToasts.reverse(); // from newest to oldest } - layout(dimension: Dimension): void { + layout(dimension: Dimension | undefined): void { this.workbenchDimensions = dimension; const maxDimensions = this.computeMaxDimensions(); @@ -525,10 +535,11 @@ export class NotificationsToasts extends Themable { return; } + const notificationsToastsContainer = assertIsDefined(this.notificationsToastsContainer); if (visible) { - this.notificationsToastsContainer.appendChild(toast.container); + notificationsToastsContainer.appendChild(toast.container); } else { - this.notificationsToastsContainer.removeChild(toast.container); + notificationsToastsContainer.removeChild(toast.container); } } diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 1c8fc26b2b..ecc291fd57 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -21,7 +21,7 @@ import { ActivePanelContext, PanelPositionContext } from 'vs/workbench/common/pa export class ClosePanelAction extends Action { static readonly ID = 'workbench.action.closePanel'; - static LABEL = nls.localize('closePanel', "Close Panel"); + static readonly LABEL = nls.localize('closePanel', "Close Panel"); constructor( id: string, @@ -40,7 +40,7 @@ export class ClosePanelAction extends Action { export class TogglePanelAction extends Action { static readonly ID = 'workbench.action.togglePanel'; - static LABEL = nls.localize('togglePanel', "Toggle Panel"); + static readonly LABEL = nls.localize('togglePanel', "Toggle Panel"); constructor( id: string, @@ -209,7 +209,7 @@ export class SwitchPanelViewAction extends Action { export class PreviousPanelViewAction extends SwitchPanelViewAction { static readonly ID = 'workbench.action.previousPanelView'; - static LABEL = nls.localize('previousPanelView', 'Previous Panel View'); + static readonly LABEL = nls.localize('previousPanelView', 'Previous Panel View'); constructor( id: string, @@ -227,7 +227,7 @@ export class PreviousPanelViewAction extends SwitchPanelViewAction { export class NextPanelViewAction extends SwitchPanelViewAction { static readonly ID = 'workbench.action.nextPanelView'; - static LABEL = nls.localize('nextPanelView', 'Next Panel View'); + static readonly LABEL = nls.localize('nextPanelView', 'Next Panel View'); constructor( id: string, diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 4c5adad979..09578e3730 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/panelpart'; import { IAction } from 'vs/base/common/actions'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { Registry } from 'vs/platform/registry/common/platform'; import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { IPanel, ActivePanelContext, PanelFocusContext } from 'vs/workbench/common/panel'; @@ -30,7 +30,7 @@ import { Dimension, trackFocus } from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { isUndefinedOrNull, withUndefinedAsNull, withNullAsUndefined } from 'vs/base/common/types'; +import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -60,28 +60,25 @@ export class PanelPart extends CompositePart implements IPanelService { readonly snap = true; get preferredHeight(): number | undefined { - const sidebarDimension = this.layoutService.getDimension(Parts.SIDEBAR_PART); - return sidebarDimension.height * 0.4; + // Don't worry about titlebar or statusbar visibility + // The difference is minimal and keeps this function clean + return this.layoutService.dimension.height * 0.4; } get preferredWidth(): number | undefined { - const statusbarPart = this.layoutService.getDimension(Parts.STATUSBAR_PART); - return statusbarPart.width * 0.4; + return this.layoutService.dimension.width * 0.4; } //#endregion - get onDidPanelOpen(): Event<{ panel: IPanel, focus: boolean }> { return Event.map(this.onDidCompositeOpen.event, compositeOpen => ({ panel: compositeOpen.composite, focus: compositeOpen.focus })); } + get onDidPanelOpen(): Event<{ panel: IPanel, focus: boolean; }> { return Event.map(this.onDidCompositeOpen.event, compositeOpen => ({ panel: compositeOpen.composite, focus: compositeOpen.focus })); } readonly onDidPanelClose: Event = this.onDidCompositeClose.event; - private _onDidVisibilityChange = this._register(new Emitter()); - readonly onDidVisibilityChange: Event = this._onDidVisibilityChange.event; - private activePanelContextKey: IContextKey; private panelFocusContextKey: IContextKey; private compositeBar: CompositeBar; - private compositeActions: Map = new Map(); + private compositeActions: Map = new Map(); private blockOpeningPanel = false; private _contentDimension: Dimension | undefined; @@ -123,7 +120,7 @@ export class PanelPart extends CompositePart implements IPanelService { openComposite: (compositeId: string) => Promise.resolve(this.openPanel(compositeId, true)), getActivityAction: (compositeId: string) => this.getCompositeActions(compositeId).activityAction, getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction, - getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(PanelActivityAction, this.getPanel(compositeId)), + getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(PanelActivityAction, assertIsDefined(this.getPanel(compositeId))), getContextMenuActions: () => [ this.instantiationService.createInstance(TogglePanelPositionAction, TogglePanelPositionAction.ID, TogglePanelPositionAction.LABEL), this.instantiationService.createInstance(TogglePanelAction, TogglePanelAction.ID, localize('hidePanel', "Hide Panel")) @@ -208,19 +205,19 @@ export class PanelPart extends CompositePart implements IPanelService { updateStyles(): void { super.updateStyles(); - const container = this.getContainer(); - container.style.backgroundColor = this.getColor(PANEL_BACKGROUND); - container.style.borderLeftColor = this.getColor(PANEL_BORDER) || this.getColor(contrastBorder); + const container = assertIsDefined(this.getContainer()); + container.style.backgroundColor = this.getColor(PANEL_BACKGROUND) || ''; + container.style.borderLeftColor = this.getColor(PANEL_BORDER) || this.getColor(contrastBorder) || ''; const title = this.getTitleArea(); if (title) { - title.style.borderTopColor = this.getColor(PANEL_BORDER) || this.getColor(contrastBorder); + title.style.borderTopColor = this.getColor(PANEL_BORDER) || this.getColor(contrastBorder) || ''; } } - openPanel(id: string, focus?: boolean): Panel | null { + openPanel(id: string, focus?: boolean): Panel | undefined { if (this.blockOpeningPanel) { - return null; // Workaround against a potential race condition + return undefined; // Workaround against a potential race condition } // First check if panel is hidden and show if so @@ -233,7 +230,7 @@ export class PanelPart extends CompositePart implements IPanelService { } } - return withUndefinedAsNull(this.openComposite(id, focus)); + return this.openComposite(id, focus); } showActivity(panelId: string, badge: IBadge, clazz?: string): IDisposable { @@ -241,15 +238,15 @@ export class PanelPart extends CompositePart implements IPanelService { } getPanel(panelId: string): IPanelIdentifier | undefined { - return withNullAsUndefined(Registry.as(PanelExtensions.Panels).getPanel(panelId)); + return Registry.as(PanelExtensions.Panels).getPanel(panelId); } - getPanels(): PanelDescriptor[] { + getPanels(): readonly PanelDescriptor[] { return Registry.as(PanelExtensions.Panels).getPanels() .sort((v1, v2) => typeof v1.order === 'number' && typeof v2.order === 'number' ? v1.order - v2.order : NaN); } - getPinnedPanels(): PanelDescriptor[] { + getPinnedPanels(): readonly PanelDescriptor[] { const pinnedCompositeIds = this.compositeBar.getPinnedComposites().map(c => c.id); return this.getPanels() .filter(p => pinnedCompositeIds.indexOf(p.id) !== -1) @@ -263,7 +260,7 @@ export class PanelPart extends CompositePart implements IPanelService { ]; } - getActivePanel(): IPanel | null { + getActivePanel(): IPanel | undefined { return this.getActiveComposite(); } @@ -303,9 +300,9 @@ export class PanelPart extends CompositePart implements IPanelService { } if (this.layoutService.getPanelPosition() === Position.RIGHT) { - this._contentDimension = new Dimension(width - 1, height!); // Take into account the 1px border when layouting + this._contentDimension = new Dimension(width - 1, height); // Take into account the 1px border when layouting } else { - this._contentDimension = new Dimension(width, height!); + this._contentDimension = new Dimension(width, height); } // Layout contents @@ -316,7 +313,7 @@ export class PanelPart extends CompositePart implements IPanelService { } private layoutCompositeBar(): void { - if (this._contentDimension) { + if (this._contentDimension && this.dimension) { let availableWidth = this._contentDimension.width - 40; // take padding into account if (this.toolBar) { availableWidth = Math.max(PanelPart.MIN_COMPOSITE_BAR_WIDTH, availableWidth - this.getToolbarWidth()); // adjust height for global actions showing @@ -326,11 +323,11 @@ export class PanelPart extends CompositePart implements IPanelService { } } - private getCompositeActions(compositeId: string): { activityAction: PanelActivityAction, pinnedAction: ToggleCompositePinnedAction } { + private getCompositeActions(compositeId: string): { activityAction: PanelActivityAction, pinnedAction: ToggleCompositePinnedAction; } { let compositeActions = this.compositeActions.get(compositeId); if (!compositeActions) { compositeActions = { - activityAction: this.instantiationService.createInstance(PanelActivityAction, this.getPanel(compositeId)), + activityAction: this.instantiationService.createInstance(PanelActivityAction, assertIsDefined(this.getPanel(compositeId))), pinnedAction: new ToggleCompositePinnedAction(this.getPanel(compositeId), this.compositeBar) }; @@ -357,7 +354,7 @@ export class PanelPart extends CompositePart implements IPanelService { private getToolbarWidth(): number { const activePanel = this.getActivePanel(); - if (!activePanel) { + if (!activePanel || !this.toolBar) { return 0; } @@ -446,10 +443,6 @@ export class PanelPart extends CompositePart implements IPanelService { this.storageService.store(PanelPart.PINNED_PANELS, value, StorageScope.GLOBAL); } - setVisible(visible: boolean): void { - this._onDidVisibilityChange.fire(visible); - } - toJSON(): object { return { type: Parts.PANEL_PART diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index 50855dc88c..61fe89a09f 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -305,8 +305,8 @@ class QuickInput extends Disposable implements IQuickInput { this.ui.inputBox.showDecoration(severity); if (severity === Severity.Error) { const styles = this.ui.inputBox.stylesForType(severity); - this.ui.message.style.backgroundColor = styles.background ? `${styles.background}` : null; - this.ui.message.style.border = styles.border ? `1px solid ${styles.border}` : null; + this.ui.message.style.backgroundColor = styles.background ? `${styles.background}` : ''; + this.ui.message.style.border = styles.border ? `1px solid ${styles.border}` : ''; this.ui.message.style.paddingBottom = '4px'; } else { this.ui.message.style.backgroundColor = ''; @@ -323,7 +323,7 @@ class QuickInput extends Disposable implements IQuickInput { class QuickPick extends QuickInput implements IQuickPick { - private static INPUT_BOX_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results."); + private static readonly INPUT_BOX_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results."); private _value = ''; private _placeholder: string; @@ -762,7 +762,7 @@ class QuickPick extends QuickInput implements IQuickPi class InputBox extends QuickInput implements IInputBox { - private static noPromptMessage = localize('inputModeEntry', "Press 'Enter' to confirm your input or 'Escape' to cancel"); + private static readonly noPromptMessage = localize('inputModeEntry', "Press 'Enter' to confirm your input or 'Escape' to cancel"); private _value = ''; private _valueSelection: Readonly<[number, number]>; @@ -1511,16 +1511,16 @@ export class QuickInputService extends Component implements IQuickInputService { if (this.ui) { // TODO const titleColor = { dark: 'rgba(255, 255, 255, 0.105)', light: 'rgba(0,0,0,.06)', hc: 'black' }[theme.type]; - this.titleBar.style.backgroundColor = titleColor ? titleColor.toString() : null; + this.titleBar.style.backgroundColor = titleColor ? titleColor.toString() : ''; this.ui.inputBox.style(theme); const quickInputBackground = theme.getColor(QUICK_INPUT_BACKGROUND); - this.ui.container.style.backgroundColor = quickInputBackground ? quickInputBackground.toString() : null; + this.ui.container.style.backgroundColor = quickInputBackground ? quickInputBackground.toString() : ''; const quickInputForeground = theme.getColor(QUICK_INPUT_FOREGROUND); this.ui.container.style.color = quickInputForeground ? quickInputForeground.toString() : null; const contrastBorderColor = theme.getColor(contrastBorder); - this.ui.container.style.border = contrastBorderColor ? `1px solid ${contrastBorderColor}` : null; + this.ui.container.style.border = contrastBorderColor ? `1px solid ${contrastBorderColor}` : ''; const widgetShadowColor = theme.getColor(widgetShadow); - this.ui.container.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : null; + this.ui.container.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : ''; } } diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts index 91124709b5..e8a679391a 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts @@ -11,7 +11,7 @@ import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IMatch } from 'vs/base/common/filters'; -import { matchesFuzzyOcticonAware, parseOcticons } from 'vs/base/common/octicon'; +import { matchesFuzzyCodiconAware, parseCodicons } from 'vs/base/common/codicon'; import { compareAnything } from 'vs/base/common/comparers'; import { Emitter, Event } from 'vs/base/common/event'; import { assign } from 'vs/base/common/objects'; @@ -114,7 +114,7 @@ class ListElementRenderer implements IListRenderer s && parseOcticons(s).text) + .map(s => s && parseCodicons(s).text) .filter(s => !!s) .join(', ')); // Separator if (element.separator && element.separator.label) { data.separator.textContent = element.separator.label; - data.separator.style.display = null; + data.separator.style.display = ''; } else { data.separator.style.display = 'none'; } @@ -490,12 +490,12 @@ export class QuickInputList { }); } - // Filter by value (since we support octicons, use octicon aware fuzzy matching) + // Filter by value (since we support codicons, use codicon aware fuzzy matching) else { this.elements.forEach(element => { - const labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesFuzzyOcticonAware(query, parseOcticons(element.saneLabel))) : undefined; - const descriptionHighlights = this.matchOnDescription ? withNullAsUndefined(matchesFuzzyOcticonAware(query, parseOcticons(element.saneDescription || ''))) : undefined; - const detailHighlights = this.matchOnDetail ? withNullAsUndefined(matchesFuzzyOcticonAware(query, parseOcticons(element.saneDetail || ''))) : undefined; + const labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesFuzzyCodiconAware(query, parseCodicons(element.saneLabel))) : undefined; + const descriptionHighlights = this.matchOnDescription ? withNullAsUndefined(matchesFuzzyCodiconAware(query, parseCodicons(element.saneDescription || ''))) : undefined; + const detailHighlights = this.matchOnDetail ? withNullAsUndefined(matchesFuzzyCodiconAware(query, parseCodicons(element.saneDetail || ''))) : undefined; if (labelHighlights || descriptionHighlights || detailHighlights) { element.labelHighlights = labelHighlights; diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index 84c4ed7c79..26fd0012cb 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -69,21 +69,21 @@ export class QuickOpenController extends Component implements IQuickOpenService private readonly _onHide: Emitter = this._register(new Emitter()); readonly onHide: Event = this._onHide.event; - private preserveInput: boolean; - private isQuickOpen: boolean; - private lastInputValue: string; - private lastSubmittedInputValue: string; - private quickOpenWidget: QuickOpenWidget; + private preserveInput: boolean | undefined; + private isQuickOpen: boolean | undefined; + private lastInputValue: string | undefined; + private lastSubmittedInputValue: string | undefined; + private quickOpenWidget: QuickOpenWidget | undefined; private mapResolvedHandlersToPrefix: Map> = new Map(); private mapContextKeyToContext: Map> = new Map(); private handlerOnOpenCalled: Set = new Set(); private promisesToCompleteOnHide: ValueCallback[] = []; - private previousActiveHandlerDescriptor: QuickOpenHandlerDescriptor | null; + private previousActiveHandlerDescriptor: QuickOpenHandlerDescriptor | null | undefined; private actionProvider = new ContributableActionProvider(); - private closeOnFocusLost: boolean; - private searchInEditorHistory: boolean; + private closeOnFocusLost: boolean | undefined; + private searchInEditorHistory: boolean | undefined; private editorHistoryHandler: EditorHistoryHandler; - private pendingGetResultsInvocation: CancellationTokenSource | null; + private pendingGetResultsInvocation: CancellationTokenSource | null = null; constructor( @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @@ -107,7 +107,7 @@ export class QuickOpenController extends Component implements IQuickOpenService private registerListeners(): void { this._register(this.configurationService.onDidChangeConfiguration(() => this.updateConfiguration())); - this._register(this.layoutService.onTitleBarVisibilityChange(() => this.positionQuickOpenWidget())); + this._register(this.layoutService.onPartVisibilityChange(() => this.positionQuickOpenWidget())); this._register(browser.onDidChangeZoomLevel(() => this.positionQuickOpenWidget())); this._register(this.layoutService.onLayout(dimension => this.layout(dimension))); } @@ -173,12 +173,12 @@ export class QuickOpenController extends Component implements IQuickOpenService // Create upon first open if (!this.quickOpenWidget) { - this.quickOpenWidget = this._register(new QuickOpenWidget( + const quickOpenWidget: QuickOpenWidget = this.quickOpenWidget = this._register(new QuickOpenWidget( this.layoutService.getWorkbenchElement(), { onOk: () => this.onOk(), onCancel: () => { /* ignore */ }, - onType: (value: string) => this.onType(value || ''), + onType: (value: string) => this.onType(quickOpenWidget, value || ''), onShow: () => this.handleOnShow(), onHide: (reason) => this.handleOnHide(reason), onFocusLost: () => !this.closeOnFocusLost @@ -186,8 +186,7 @@ export class QuickOpenController extends Component implements IQuickOpenService inputPlaceHolder: this.hasHandler(HELP_PREFIX) ? nls.localize('quickOpenInput', "Type '?' to get help on the actions you can take from here") : '', keyboardSupport: false, treeCreator: (container, config, opts) => this.instantiationService.createInstance(WorkbenchTree, container, config, opts) - } - )); + })); this._register(attachQuickOpenStyler(this.quickOpenWidget, this.themeService, { background: QUICK_INPUT_BACKGROUND, foreground: QUICK_INPUT_FOREGROUND })); const quickOpenContainer = this.quickOpenWidget.create(); @@ -307,7 +306,7 @@ export class QuickOpenController extends Component implements IQuickOpenService } } - if (key && key.get()) { + if (key?.get()) { return; // already active context } @@ -339,7 +338,7 @@ export class QuickOpenController extends Component implements IQuickOpenService } } - private onType(value: string): void { + private onType(quickOpenWidget: QuickOpenWidget, value: string): void { // cancel any pending get results invocation and create new this.cancelPendingGetResultsInvocation(); @@ -351,16 +350,16 @@ export class QuickOpenController extends Component implements IQuickOpenService const registry = Registry.as(Extensions.Quickopen); const handlerDescriptor = registry.getQuickOpenHandler(value); const defaultHandlerDescriptor = registry.getDefaultQuickOpenHandler(); - const instantProgress = handlerDescriptor && handlerDescriptor.instantProgress; + const instantProgress = handlerDescriptor?.instantProgress; const contextKey = handlerDescriptor ? handlerDescriptor.contextKey : defaultHandlerDescriptor.contextKey; // Reset Progress if (!instantProgress) { - this.quickOpenWidget.getProgressBar().stop().hide(); + quickOpenWidget.getProgressBar().stop().hide(); } // Reset Extra Class - this.quickOpenWidget.setExtraClass(null); + quickOpenWidget.setExtraClass(null); // Update context this.setQuickOpenContextKey(contextKey); @@ -374,7 +373,7 @@ export class QuickOpenController extends Component implements IQuickOpenService // Trigger onOpen this.resolveHandler(handlerDescriptor || defaultHandlerDescriptor); - this.quickOpenWidget.setInput(this.getEditorHistoryWithGroupLabel(), { autoFocusFirstEntry: true }); + quickOpenWidget.setInput(this.getEditorHistoryWithGroupLabel(), { autoFocusFirstEntry: true }); // If quickOpen entered empty we have to clear the prefill-cache this.lastInputValue = ''; @@ -388,7 +387,7 @@ export class QuickOpenController extends Component implements IQuickOpenService if (handlerDescriptor) { this.isQuickOpen = false; - resultPromise = this.handleSpecificHandler(handlerDescriptor, value, pendingResultsInvocationToken); + resultPromise = this.handleSpecificHandler(quickOpenWidget, handlerDescriptor, value, pendingResultsInvocationToken); } // Otherwise handle default handlers if no specific handler present @@ -396,7 +395,7 @@ export class QuickOpenController extends Component implements IQuickOpenService this.isQuickOpen = true; // Cache the value for prefilling the quickOpen next time is opened this.lastInputValue = trimmedValue; - resultPromise = this.handleDefaultHandler(defaultHandlerDescriptor, value, pendingResultsInvocationToken); + resultPromise = this.handleDefaultHandler(quickOpenWidget, defaultHandlerDescriptor, value, pendingResultsInvocationToken); } // Remember as the active one @@ -405,7 +404,7 @@ export class QuickOpenController extends Component implements IQuickOpenService // Progress if task takes a long time setTimeout(() => { if (!resultPromiseDone && !pendingResultsInvocationToken.isCancellationRequested) { - this.quickOpenWidget.getProgressBar().infinite().show(); + quickOpenWidget.getProgressBar().infinite().show(); } }, instantProgress ? 0 : 800); @@ -414,7 +413,7 @@ export class QuickOpenController extends Component implements IQuickOpenService resultPromiseDone = true; if (!pendingResultsInvocationToken.isCancellationRequested) { - this.quickOpenWidget.getProgressBar().hide(); + quickOpenWidget.getProgressBar().hide(); } pendingResultsInvocationTokenSource.dispose(); @@ -428,7 +427,7 @@ export class QuickOpenController extends Component implements IQuickOpenService }); } - private async handleDefaultHandler(handler: QuickOpenHandlerDescriptor, value: string, token: CancellationToken): Promise { + private async handleDefaultHandler(quickOpenWidget: QuickOpenWidget, handler: QuickOpenHandlerDescriptor, value: string, token: CancellationToken): Promise { // Fill in history results if matching and we are configured to search in history let matchingHistoryEntries: QuickOpenEntry[]; @@ -451,8 +450,8 @@ export class QuickOpenController extends Component implements IQuickOpenService // If we have matching entries from history we want to show them directly and not wait for the other results to come in // This also applies when we used to have entries from a previous run and now there are no more history results matching - const previousInput = this.quickOpenWidget.getInput(); - const wasShowingHistory = previousInput && previousInput.entries && previousInput.entries.some(e => e instanceof EditorHistoryEntry || e instanceof EditorHistoryEntryGroup); + const previousInput = quickOpenWidget.getInput(); + const wasShowingHistory = previousInput?.entries?.some(e => e instanceof EditorHistoryEntry || e instanceof EditorHistoryEntryGroup); if (wasShowingHistory || matchingHistoryEntries.length > 0) { (async () => { if (resolvedHandler.hasShortResponseTime()) { @@ -460,7 +459,7 @@ export class QuickOpenController extends Component implements IQuickOpenService } if (!token.isCancellationRequested && !inputSet) { - this.quickOpenWidget.setInput(quickOpenModel, { autoFocusFirstEntry: true }); + quickOpenWidget.setInput(quickOpenModel, { autoFocusFirstEntry: true }); inputSet = true; } })(); @@ -472,17 +471,17 @@ export class QuickOpenController extends Component implements IQuickOpenService // now is the time to show the input if we did not have set it before if (!inputSet) { - this.quickOpenWidget.setInput(quickOpenModel, { autoFocusFirstEntry: true }); + quickOpenWidget.setInput(quickOpenModel, { autoFocusFirstEntry: true }); inputSet = true; } // merge history and default handler results - const handlerResults = (result && result.entries) || []; - this.mergeResults(quickOpenModel, handlerResults, types.withNullAsUndefined(resolvedHandler.getGroupLabel())); + const handlerResults = result?.entries || []; + this.mergeResults(quickOpenWidget, quickOpenModel, handlerResults, types.withNullAsUndefined(resolvedHandler.getGroupLabel())); } } - private mergeResults(quickOpenModel: QuickOpenModel, handlerResults: QuickOpenEntry[], groupLabel: string | undefined): void { + private mergeResults(quickOpenWidget: QuickOpenWidget, quickOpenModel: QuickOpenModel, handlerResults: QuickOpenEntry[], groupLabel: string | undefined): void { // Remove results already showing by checking for a "resource" property const mapEntryToResource = this.mapEntriesToResource(quickOpenModel); @@ -501,17 +500,17 @@ export class QuickOpenController extends Component implements IQuickOpenService const useTopBorder = quickOpenModel.getEntries().length > 0; additionalHandlerResults[0] = new QuickOpenEntryGroup(additionalHandlerResults[0], groupLabel, useTopBorder); quickOpenModel.addEntries(additionalHandlerResults); - this.quickOpenWidget.refresh(quickOpenModel, { autoFocusFirstEntry }); + quickOpenWidget.refresh(quickOpenModel, { autoFocusFirstEntry }); } // Otherwise if no results are present (even from histoy) indicate this to the user else if (quickOpenModel.getEntries().length === 0) { quickOpenModel.addEntries([new PlaceholderQuickOpenEntry(nls.localize('noResultsFound1', "No results found"))]); - this.quickOpenWidget.refresh(quickOpenModel, { autoFocusFirstEntry: true }); + quickOpenWidget.refresh(quickOpenModel, { autoFocusFirstEntry: true }); } } - private async handleSpecificHandler(handlerDescriptor: QuickOpenHandlerDescriptor, value: string, token: CancellationToken): Promise { + private async handleSpecificHandler(quickOpenWidget: QuickOpenWidget, handlerDescriptor: QuickOpenHandlerDescriptor, value: string, token: CancellationToken): Promise { const resolvedHandler = await this.resolveHandler(handlerDescriptor); // Remove handler prefix from search value @@ -523,7 +522,7 @@ export class QuickOpenController extends Component implements IQuickOpenService const placeHolderLabel = (typeof canRun === 'string') ? canRun : nls.localize('canNotRunPlaceholder', "This quick open handler can not be used in the current context"); const model = new QuickOpenModel([new PlaceholderQuickOpenEntry(placeHolderLabel)], this.actionProvider); - this.showModel(model, resolvedHandler.getAutoFocus(value, { model, quickNavigateConfiguration: this.quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel())); + this.showModel(quickOpenWidget, model, resolvedHandler.getAutoFocus(value, { model, quickNavigateConfiguration: quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel())); return; } @@ -531,12 +530,12 @@ export class QuickOpenController extends Component implements IQuickOpenService // Support extra class from handler const extraClass = resolvedHandler.getClass(); if (extraClass) { - this.quickOpenWidget.setExtraClass(extraClass); + quickOpenWidget.setExtraClass(extraClass); } // When handlers change, clear the result list first before loading the new results if (this.previousActiveHandlerDescriptor !== handlerDescriptor) { - this.clearModel(); + this.clearModel(quickOpenWidget); } // Receive Results from Handler and apply @@ -544,28 +543,28 @@ export class QuickOpenController extends Component implements IQuickOpenService if (!token.isCancellationRequested) { if (!result || !result.entries.length) { const model = new QuickOpenModel([new PlaceholderQuickOpenEntry(resolvedHandler.getEmptyLabel(value))]); - this.showModel(model, resolvedHandler.getAutoFocus(value, { model, quickNavigateConfiguration: this.quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel())); + this.showModel(quickOpenWidget, model, resolvedHandler.getAutoFocus(value, { model, quickNavigateConfiguration: quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel())); } else { - this.showModel(result, resolvedHandler.getAutoFocus(value, { model: result, quickNavigateConfiguration: this.quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel())); + this.showModel(quickOpenWidget, result, resolvedHandler.getAutoFocus(value, { model: result, quickNavigateConfiguration: quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel())); } } } - private showModel(model: IModel, autoFocus?: IAutoFocus, ariaLabel?: string): void { + private showModel(quickOpenWidget: QuickOpenWidget, model: IModel, autoFocus?: IAutoFocus, ariaLabel?: string): void { // If the given model is already set in the widget, refresh and return early - if (this.quickOpenWidget.getInput() === model) { - this.quickOpenWidget.refresh(model, autoFocus); + if (quickOpenWidget.getInput() === model) { + quickOpenWidget.refresh(model, autoFocus); return; } // Otherwise just set it - this.quickOpenWidget.setInput(model, autoFocus, ariaLabel); + quickOpenWidget.setInput(model, autoFocus, ariaLabel); } - private clearModel(): void { - this.showModel(new QuickOpenModel(), undefined); + private clearModel(quickOpenWidget: QuickOpenWidget): void { + this.showModel(quickOpenWidget, new QuickOpenModel(), undefined); } private mapEntriesToResource(model: QuickOpenModel): { [resource: string]: QuickOpenEntry; } { diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 88ce5065b5..6c7524f61c 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -32,6 +32,7 @@ import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { LayoutPriority } from 'vs/base/browser/ui/grid/grid'; +import { assertIsDefined } from 'vs/base/common/types'; export class SidebarPart extends CompositePart implements IViewletService { @@ -58,7 +59,6 @@ export class SidebarPart extends CompositePart implements IViewletServi } const width = viewlet.getOptimalWidth(); - if (typeof width !== 'number') { return undefined; // {{SQL CARBON EDIT}} strict-null-check } @@ -70,9 +70,6 @@ export class SidebarPart extends CompositePart implements IViewletServi get onDidViewletRegister(): Event { return >this.viewletRegistry.onDidRegister; } - private _onDidVisibilityChange = this._register(new Emitter()); - readonly onDidVisibilityChange: Event = this._onDidVisibilityChange.event; - private _onDidViewletDeregister = this._register(new Emitter()); readonly onDidViewletDeregister: Event = this._onDidViewletDeregister.event; @@ -173,19 +170,19 @@ export class SidebarPart extends CompositePart implements IViewletServi super.updateStyles(); // Part container - const container = this.getContainer(); + const container = assertIsDefined(this.getContainer()); - container.style.backgroundColor = this.getColor(SIDE_BAR_BACKGROUND); + container.style.backgroundColor = this.getColor(SIDE_BAR_BACKGROUND) || ''; container.style.color = this.getColor(SIDE_BAR_FOREGROUND); const borderColor = this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder); const isPositionLeft = this.layoutService.getSideBarPosition() === SideBarPosition.LEFT; - container.style.borderRightWidth = borderColor && isPositionLeft ? '1px' : null; - container.style.borderRightStyle = borderColor && isPositionLeft ? 'solid' : null; - container.style.borderRightColor = isPositionLeft ? borderColor : null; - container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : null; - container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : null; - container.style.borderLeftColor = !isPositionLeft ? borderColor : null; + container.style.borderRightWidth = borderColor && isPositionLeft ? '1px' : ''; + container.style.borderRightStyle = borderColor && isPositionLeft ? 'solid' : ''; + container.style.borderRightColor = isPositionLeft ? borderColor || '' : ''; + container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : ''; + container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : ''; + container.style.borderLeftColor = !isPositionLeft ? borderColor || '' : ''; } layout(width: number, height: number): void { @@ -198,7 +195,7 @@ export class SidebarPart extends CompositePart implements IViewletServi // Viewlet service - getActiveViewlet(): IViewlet | null { + getActiveViewlet(): IViewlet | undefined { return this.getActiveComposite(); } @@ -210,7 +207,7 @@ export class SidebarPart extends CompositePart implements IViewletServi this.hideActiveComposite(); } - async openViewlet(id: string | undefined, focus?: boolean): Promise { + async openViewlet(id: string | undefined, focus?: boolean): Promise { if (typeof id === 'string' && this.getViewlet(id)) { return this.doOpenViewlet(id, focus); } @@ -221,12 +218,21 @@ export class SidebarPart extends CompositePart implements IViewletServi return this.doOpenViewlet(id, focus); } - return null; + return undefined; } getViewlets(): ViewletDescriptor[] { - return this.viewletRegistry.getViewlets() - .sort((v1, v2) => v1.order! - v2.order!); + return this.viewletRegistry.getViewlets().sort((v1, v2) => { + if (typeof v1.order !== 'number') { + return -1; + } + + if (typeof v2.order !== 'number') { + return 1; + } + + return v1.order - v2.order; + }); } getDefaultViewletId(): string { @@ -237,9 +243,9 @@ export class SidebarPart extends CompositePart implements IViewletServi return this.getViewlets().filter(viewlet => viewlet.id === id)[0]; } - private doOpenViewlet(id: string, focus?: boolean): Viewlet | null { + private doOpenViewlet(id: string, focus?: boolean): Viewlet | undefined { if (this.blockOpeningViewlet) { - return null; // Workaround against a potential race condition + return undefined; // Workaround against a potential race condition } // First check if sidebar is hidden and show if so @@ -275,10 +281,6 @@ export class SidebarPart extends CompositePart implements IViewletServi } } - setVisible(visible: boolean): void { - this._onDidVisibilityChange.fire(visible); - } - toJSON(): object { return { type: Parts.SIDEBAR_PART @@ -308,7 +310,7 @@ class FocusSideBarAction extends Action { } // Focus into active viewlet - let viewlet = this.viewletService.getActiveViewlet(); + const viewlet = this.viewletService.getActiveViewlet(); if (viewlet) { viewlet.focus(); } diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index 8d5740a839..035487680f 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -104,7 +104,8 @@ pointer-events: none; } -.monaco-workbench .part.statusbar > .items-container > .statusbar-item span.octicon { +.monaco-workbench .part.statusbar > .items-container > .statusbar-item span.codicon { text-align: center; font-size: 14px; + color: inherit; } diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 5dfe4c4888..5be6b7b2e0 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/statusbarpart'; import * as nls from 'vs/nls'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { dispose, IDisposable, Disposable, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; +import { CodiconLabel } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Part } from 'vs/workbench/browser/part'; @@ -27,11 +27,12 @@ import { INotificationService } from 'vs/platform/notification/common/notificati 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 { coalesce, find } 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'; +import { assertIsDefined } from 'vs/base/common/types'; interface IPendingStatusbarEntry { id: string; @@ -175,13 +176,7 @@ class StatusbarViewModel extends Disposable { } findEntry(container: HTMLElement): IStatusbarViewModelEntry | undefined { - for (const entry of this._entries) { - if (entry.container === container) { - return entry; - } - } - - return undefined; + return find(this._entries, entry => entry.container === container); } getEntries(alignment: StatusbarAlignment): IStatusbarViewModelEntry[] { @@ -238,7 +233,10 @@ class StatusbarViewModel extends Disposable { return entryB.priority - entryA.priority; // higher priority towards the left } - return mapEntryToIndex.get(entryA)! - mapEntryToIndex.get(entryB)!; // otherwise maintain stable order + const indexA = mapEntryToIndex.get(entryA); + const indexB = mapEntryToIndex.get(entryB); + + return indexA! - indexB!; // otherwise maintain stable order (both values known to be in map) } if (entryA.alignment === StatusbarAlignment.LEFT) { @@ -310,8 +308,8 @@ class ToggleStatusbarEntryVisibilityAction extends Action { class HideStatusbarEntryAction extends Action { - constructor(id: string, private model: StatusbarViewModel) { - super(id, nls.localize('hide', "Hide"), undefined, true); + constructor(id: string, name: string, private model: StatusbarViewModel) { + super(id, nls.localize('hide', "Hide '{0}'", name), undefined, true); } run(): Promise { @@ -334,14 +332,14 @@ export class StatusbarPart extends Part implements IStatusbarService { //#endregion - private styleElement!: HTMLStyleElement; + private styleElement: HTMLStyleElement | undefined; private pendingEntries: IPendingStatusbarEntry[] = []; private readonly viewModel: StatusbarViewModel; - private leftItemsContainer!: HTMLElement; - private rightItemsContainer!: HTMLElement; + private leftItemsContainer: HTMLElement | undefined; + private rightItemsContainer: HTMLElement | undefined; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -475,7 +473,7 @@ export class StatusbarPart extends Part implements IStatusbarService { ...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; + const target = assertIsDefined(entry.alignment === StatusbarAlignment.LEFT ? this.leftItemsContainer : this.rightItemsContainer); target.appendChild(entry.container); }); @@ -488,7 +486,7 @@ export class StatusbarPart extends Part implements IStatusbarService { entries.reverse(); // reversing due to flex: row-reverse } - const target = alignment === StatusbarAlignment.LEFT ? this.leftItemsContainer : this.rightItemsContainer; + const target = assertIsDefined(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 @@ -562,7 +560,7 @@ export class StatusbarPart extends Part implements IStatusbarService { if (statusEntryUnderMouse) { actions.push(new Separator()); - actions.push(new HideStatusbarEntryAction(statusEntryUnderMouse.id, this.viewModel)); + actions.push(new HideStatusbarEntryAction(statusEntryUnderMouse.id, statusEntryUnderMouse.name, this.viewModel)); } return actions; @@ -571,10 +569,10 @@ export class StatusbarPart extends Part implements IStatusbarService { updateStyles(): void { super.updateStyles(); - const container = this.getContainer(); + const container = assertIsDefined(this.getContainer()); // Background colors - const backgroundColor = this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_BACKGROUND : STATUS_BAR_NO_FOLDER_BACKGROUND); + const backgroundColor = this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_BACKGROUND : STATUS_BAR_NO_FOLDER_BACKGROUND) || ''; container.style.backgroundColor = backgroundColor; container.style.color = this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_FOREGROUND : STATUS_BAR_NO_FOLDER_FOREGROUND); @@ -630,7 +628,7 @@ class StatusbarEntryItem extends Disposable { private entry!: IStatusbarEntry; private labelContainer!: HTMLElement; - private label!: OcticonLabel; + private label!: CodiconLabel; private readonly foregroundListener = this._register(new MutableDisposable()); private readonly backgroundListener = this._register(new MutableDisposable()); @@ -659,7 +657,7 @@ class StatusbarEntryItem extends Disposable { this.labelContainer.tabIndex = -1; // allows screen readers to read title, but still prevents tab focus. // Label - this.label = new OcticonLabel(this.labelContainer); + this.label = new CodiconLabel(this.labelContainer); // Add to parent this.container.appendChild(this.labelContainer); @@ -691,8 +689,9 @@ class StatusbarEntryItem extends Disposable { if (!this.entry || entry.command !== this.entry.command) { this.commandListener.clear(); - if (entry.command) { - this.commandListener.value = addDisposableListener(this.labelContainer, EventType.CLICK, () => this.executeCommand(entry.command!, entry.arguments)); + const command = entry.command; + if (command) { + this.commandListener.value = addDisposableListener(this.labelContainer, EventType.CLICK, () => this.executeCommand(command, entry.arguments)); removeClass(this.labelContainer, 'disabled'); } else { @@ -779,7 +778,7 @@ class StatusbarEntryItem extends Disposable { } if (isBackground) { - container.style.backgroundColor = colorResult; + container.style.backgroundColor = colorResult || ''; } else { container.style.color = colorResult; } diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 0763038112..3a2504b0d0 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -106,54 +106,29 @@ .monaco-workbench.fullscreen .part.titlebar > .window-controls-container { display: none; + background-color: transparent; } -.monaco-workbench .part.titlebar > .window-controls-container > .window-icon-bg { +.monaco-workbench .part.titlebar > .window-controls-container > .window-icon { display: inline-block; - -webkit-app-region: no-drag; + line-height: 30px; height: 100%; - width: 33.34%; + width: 46px; + font-size: 16px; } -.monaco-workbench .part.titlebar > .window-controls-container .window-icon svg { - shape-rendering: crispEdges; - text-align: center; -} - -.monaco-workbench .part.titlebar.titlebar > .window-controls-container .window-close { - -webkit-mask: url('chrome-close.svg') no-repeat 50% 50%; -} - -.monaco-workbench .part.titlebar.titlebar > .window-controls-container .window-unmaximize { - -webkit-mask: url('chrome-restore.svg') no-repeat 50% 50%; -} - -.monaco-workbench .part.titlebar > .window-controls-container .window-maximize { - -webkit-mask: url('chrome-maximize.svg') no-repeat 50% 50%; -} - -.monaco-workbench .part.titlebar > .window-controls-container .window-minimize { - -webkit-mask: url('chrome-minimize.svg') no-repeat 50% 50%; -} - -.monaco-workbench .part.titlebar > .window-controls-container > .window-icon-bg > .window-icon { - height: 100%; - width: 100%; - -webkit-mask-size: 23.1%; -} - -.monaco-workbench .part.titlebar > .window-controls-container > .window-icon-bg:hover { +.monaco-workbench .part.titlebar > .window-controls-container > .window-icon:hover { background-color: rgba(255, 255, 255, 0.1); } -.monaco-workbench .part.titlebar.light > .window-controls-container > .window-icon-bg:hover { +.monaco-workbench .part.titlebar.light > .window-controls-container > .window-icon:hover { background-color: rgba(0, 0, 0, 0.1); } -.monaco-workbench .part.titlebar > .window-controls-container > .window-icon-bg.window-close-bg:hover { +.monaco-workbench .part.titlebar > .window-controls-container > .window-icon.window-close:hover { background-color: rgba(232, 17, 35, 0.9); } .monaco-workbench .part.titlebar > .window-controls-container .window-icon.window-close:hover { - background-color: white; + color: white; } diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index da955a3bd2..2529e54f95 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { IMenuService, MenuId, IMenu, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { registerThemingParticipant, ITheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService'; -import { MenuBarVisibility, getTitleBarStyle, IWindowOpenable } from 'vs/platform/windows/common/windows'; +import { MenuBarVisibility, getTitleBarStyle, IWindowOpenable, getMenuBarVisibility } from 'vs/platform/windows/common/windows'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IAction, Action } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -77,11 +77,11 @@ export abstract class MenubarControl extends Disposable { 'Help': nls.localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help") }; - protected recentlyOpened: IRecentlyOpened; + protected recentlyOpened: IRecentlyOpened = { files: [], workspaces: [] }; protected menuUpdater: RunOnceScheduler; - protected static MAX_MENU_RECENT_ENTRIES = 10; + protected static readonly MAX_MENU_RECENT_ENTRIES = 10; constructor( protected readonly menuService: IMenuService, @@ -121,6 +121,9 @@ export abstract class MenubarControl extends Disposable { protected abstract doUpdateMenubar(firstTime: boolean): void; protected registerListeners(): void { + // Listen for window focus changes + this._register(this.hostService.onDidChangeFocus(e => this.onDidChangeWindowFocus(e))); + // Update when config changes this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e))); @@ -179,6 +182,13 @@ export abstract class MenubarControl extends Disposable { return result; } + protected onDidChangeWindowFocus(hasFocus: boolean): void { + // When we regain focus, update the recent menu items + if (hasFocus) { + this.onRecentlyOpenedChange(); + } + } + private onConfigurationUpdated(event: IConfigurationChangeEvent): void { if (this.keys.some(key => event.affectsConfiguration(key))) { this.updateMenubar(); @@ -260,10 +270,10 @@ export abstract class MenubarControl extends Disposable { } export class CustomMenubarControl extends MenubarControl { - private menubar: MenuBar; - private container: HTMLElement; - private alwaysOnMnemonics: boolean; - private focusInsideMenubar: boolean; + private menubar: MenuBar | undefined; + private container: HTMLElement | undefined; + private alwaysOnMnemonics: boolean = false; + private focusInsideMenubar: boolean = false; private readonly _onVisibilityChange: Emitter; private readonly _onFocusStateChange: Emitter; @@ -435,9 +445,9 @@ export class CustomMenubarControl extends MenubarControl { return null; case StateType.Idle: - const windowId = this.electronEnvironmentService.windowId; + const context = `window:${this.electronEnvironmentService ? this.electronEnvironmentService.windowId : 'any'}`; return new Action('update.check', nls.localize({ key: 'checkForUpdates', comment: ['&& denotes a mnemonic'] }, "Check for &&Updates..."), undefined, true, () => - this.updateService.checkForUpdates({ windowId })); + this.updateService.checkForUpdates(context)); case StateType.CheckingForUpdates: return new Action('update.checking', nls.localize('checkingForUpdates', "Checking for Updates..."), undefined, false); @@ -463,7 +473,7 @@ export class CustomMenubarControl extends MenubarControl { } private get currentMenubarVisibility(): MenuBarVisibility { - return this.configurationService.getValue('window.menuBarVisibility'); + return getMenuBarVisibility(this.configurationService, this.environmentService); } private get currentDisableMenuBarAltFocus(): boolean { @@ -519,6 +529,11 @@ export class CustomMenubarControl extends MenubarControl { } private setupCustomMenubar(firstTime: boolean): void { + // If there is no container, we cannot setup the menubar + if (!this.container) { + return; + } + if (firstTime) { this.menubar = this._register(new MenuBar( this.container, { @@ -527,12 +542,13 @@ export class CustomMenubarControl extends MenubarControl { visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), compactMode: this.currentCompactMenuMode - } - )); + })); this.accessibilityService.alwaysUnderlineAccessKeys().then(val => { this.alwaysOnMnemonics = val; - this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, disableAltFocus: this.currentDisableMenuBarAltFocus, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics, compactMode: this.currentCompactMenuMode }); + if (this.menubar) { + this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, disableAltFocus: this.currentDisableMenuBarAltFocus, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics, compactMode: this.currentCompactMenuMode }); + } }); this._register(this.menubar.onFocusStateChange(focused => { @@ -558,7 +574,9 @@ export class CustomMenubarControl extends MenubarControl { this._register(attachMenuStyler(this.menubar, this.themeService)); } else { - this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, disableAltFocus: this.currentDisableMenuBarAltFocus, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics, compactMode: this.currentCompactMenuMode }); + if (this.menubar) { + this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, disableAltFocus: this.currentDisableMenuBarAltFocus, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics, compactMode: this.currentCompactMenuMode }); + } } // Update the menu actions @@ -571,19 +589,20 @@ export class CustomMenubarControl extends MenubarControl { for (let action of actions) { this.insertActionsBefore(action, target); if (action instanceof SubmenuItemAction) { - 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(() => { + let submenu = this.menus[action.item.submenu]; + if (!submenu) { + submenu = this.menus[action.item.submenu] = this.menuService.createMenu(action.item.submenu, this.contextKeyService); + this._register(submenu.onDidChange(() => { if (!this.focusInsideMenubar) { const actions: IAction[] = []; updateActions(menu, actions, topLevelTitle); - this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[topLevelTitle]) }); + if (this.menubar) { + this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[topLevelTitle]) }); + } } }, this)); } - const submenu = this.menus[action.item.submenu]!; const submenuActions: SubmenuAction[] = []; updateActions(submenu, submenuActions, topLevelTitle); target.push(new SubmenuAction(mnemonicMenuLabel(action.label), submenuActions)); @@ -606,7 +625,9 @@ export class CustomMenubarControl extends MenubarControl { if (!this.focusInsideMenubar) { const actions: IAction[] = []; updateActions(menu, actions, title); - this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); + if (this.menubar) { + this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); + } } })); } @@ -616,21 +637,27 @@ export class CustomMenubarControl extends MenubarControl { updateActions(menu, actions, title); } - if (!firstTime) { - this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); - } else { - this.menubar.push({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); + if (this.menubar) { + if (!firstTime) { + this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); + } else { + this.menubar.push({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); + } } } } - private onDidChangeWindowFocus(hasFocus: boolean): void { + protected onDidChangeWindowFocus(hasFocus: boolean): void { + super.onDidChangeWindowFocus(hasFocus); + if (this.container) { if (hasFocus) { DOM.removeClass(this.container, 'inactive'); } else { DOM.addClass(this.container, 'inactive'); - this.menubar.blur(); + if (this.menubar) { + this.menubar.blur(); + } } } } @@ -638,9 +665,6 @@ export class CustomMenubarControl extends MenubarControl { protected registerListeners(): void { super.registerListeners(); - // Listen for window focus changes - this._register(this.hostService.onDidChangeFocus(e => this.onDidChangeWindowFocus(e))); - // Listen for maximize/unmaximize if (!isWeb) { this._register(Event.any( @@ -650,7 +674,9 @@ export class CustomMenubarControl extends MenubarControl { } this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, () => { - this.menubar.blur(); + if (this.menubar) { + this.menubar.blur(); + } })); // Mnemonics require fullscreen in web diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index e58a53d295..3b01b0f837 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -8,7 +8,7 @@ import * as resources from 'vs/base/common/resources'; import { Part } from 'vs/workbench/browser/part'; import { ITitleService, ITitleProperties } from 'vs/workbench/services/title/common/titleService'; import { getZoomFactor } from 'vs/base/browser/browser'; -import { MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows'; +import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility } from 'vs/platform/windows/common/windows'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IAction } from 'vs/base/common/actions'; @@ -18,7 +18,7 @@ import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; import * as nls from 'vs/nls'; import { EditorInput, toResource, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } 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, isWeb } from 'vs/base/common/platform'; @@ -68,19 +68,20 @@ export class TitlebarPart extends Part implements ITitleService { _serviceBrand: undefined; - private title: HTMLElement; - private dragRegion: HTMLElement; - private windowControls: HTMLElement; - private maxRestoreControl: HTMLElement; - private appIcon: HTMLElement; + private title!: HTMLElement; + private dragRegion: HTMLElement | undefined; + private windowControls: HTMLElement | undefined; + private maxRestoreControl: HTMLElement | undefined; + private appIcon: HTMLElement | undefined; private customMenubar: CustomMenubarControl | undefined; private menubar?: HTMLElement; - private resizer: HTMLElement; - private lastLayoutDimensions: Dimension; + private resizer: HTMLElement | undefined; + private lastLayoutDimensions: Dimension | undefined; + private titleBarStyle: 'native' | 'custom'; - private pendingTitle: string; + private pendingTitle: string | undefined; - private isInactive: boolean; + private isInactive: boolean = false; private readonly properties: ITitleProperties = { isPure: true, isAdmin: false }; private readonly activeEditorListeners = this._register(new DisposableStore()); @@ -110,6 +111,8 @@ export class TitlebarPart extends Part implements ITitleService { this.contextMenu = this._register(menuService.createMenu(MenuId.TitleBarContext, contextKeyService)); + this.titleBarStyle = getTitleBarStyle(this.configurationService, this.environmentService); + this.registerListeners(); } @@ -138,11 +141,13 @@ export class TitlebarPart extends Part implements ITitleService { this.titleUpdater.schedule(); } - if (event.affectsConfiguration('window.menuBarVisibility')) { - if (this.currentMenubarVisibility === 'compact') { - this.uninstallMenubar(); - } else { - this.installMenubar(); + if (this.titleBarStyle !== 'native') { + if (event.affectsConfiguration('window.menuBarVisibility')) { + if (this.currentMenubarVisibility === 'compact') { + this.uninstallMenubar(); + } else { + this.installMenubar(); + } } } @@ -158,8 +163,10 @@ export class TitlebarPart extends Part implements ITitleService { // Hide title when toggling menu bar if (!isWeb && this.currentMenubarVisibility === 'toggle' && visible) { // Hack to fix issue #52522 with layered webkit-app-region elements appearing under cursor - hide(this.dragRegion); - setTimeout(() => show(this.dragRegion), 50); + if (this.dragRegion) { + hide(this.dragRegion); + setTimeout(() => show(this.dragRegion!), 50); + } } this.adjustTitleMarginToCenter(); @@ -169,7 +176,7 @@ export class TitlebarPart extends Part implements ITitleService { } private onMenubarFocusChanged(focused: boolean) { - if (!isWeb && (isWindows || isLinux) && this.currentMenubarVisibility === 'compact') { + if (!isWeb && (isWindows || isLinux) && this.currentMenubarVisibility === 'compact' && this.dragRegion) { if (focused) { hide(this.dragRegion); } else { @@ -281,14 +288,22 @@ export class TitlebarPart extends Part implements ITitleService { // Compute active editor folder const editorResource = editor ? toResource(editor) : undefined; let editorFolderResource = editorResource ? resources.dirname(editorResource) : undefined; - if (editorFolderResource && editorFolderResource.path === '.') { + if (editorFolderResource?.path === '.') { editorFolderResource = undefined; } // Compute folder resource // Single Root Workspace: always the root single workspace in this case // Otherwise: root folder of the currently active file if any - const folder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER ? workspace.folders[0] : this.contextService.getWorkspaceFolder(toResource(editor, { supportSideBySide: SideBySideEditor.MASTER })!); + let folder: IWorkspaceFolder | null = null; + if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) { + folder = workspace.folders[0]; + } else { + const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); + if (resource) { + folder = this.contextService.getWorkspaceFolder(resource); + } + } // Variables const activeEditorShort = editor ? editor.getTitle(Verbosity.SHORT) : ''; @@ -301,7 +316,7 @@ export class TitlebarPart extends Part implements ITitleService { const rootPath = root ? this.labelService.getUriLabel(root) : ''; const folderName = folder ? folder.name : ''; const folderPath = folder ? this.labelService.getUriLabel(folder.uri) : ''; - const dirty = editor && editor.isDirty() ? TitlebarPart.TITLE_DIRTY : ''; + const dirty = editor?.isDirty() ? TitlebarPart.TITLE_DIRTY : ''; const appName = this.environmentService.appNameLong; const remoteName = this.environmentService.configuration.remoteAuthority; const separator = TitlebarPart.TITLE_SEPARATOR; @@ -338,6 +353,11 @@ export class TitlebarPart extends Part implements ITitleService { } private installMenubar(): void { + // If the menubar is already installed, skip + if (this.menubar) { + return; + } + this.customMenubar = this._register(this.instantiationService.createInstance(CustomMenubarControl)); this.menubar = this.element.insertBefore($('div.menubar'), this.title); @@ -370,7 +390,7 @@ export class TitlebarPart extends Part implements ITitleService { // Menubar: install a custom menu bar depending on configuration // and when not in activity bar - if (getTitleBarStyle(this.configurationService, this.environmentService) !== 'native' + if (this.titleBarStyle !== 'native' && (!isMacintosh || isWeb) && this.currentMenubarVisibility !== 'compact') { this.installMenubar(); @@ -400,17 +420,13 @@ export class TitlebarPart extends Part implements ITitleService { this.windowControls = append(this.element, $('div.window-controls-container')); // Minimize - const minimizeIconContainer = append(this.windowControls, $('div.window-icon-bg')); - const minimizeIcon = append(minimizeIconContainer, $('div.window-icon')); - addClass(minimizeIcon, 'window-minimize'); + const minimizeIcon = append(this.windowControls, $('div.window-icon.window-minimize.codicon.codicon-chrome-minimize')); this._register(addDisposableListener(minimizeIcon, EventType.CLICK, e => { this.electronService.minimizeWindow(); })); // Restore - const restoreIconContainer = append(this.windowControls, $('div.window-icon-bg')); - this.maxRestoreControl = append(restoreIconContainer, $('div.window-icon')); - addClass(this.maxRestoreControl, 'window-max-restore'); + this.maxRestoreControl = append(this.windowControls, $('div.window-icon.window-max-restore.codicon')); this._register(addDisposableListener(this.maxRestoreControl, EventType.CLICK, async e => { const maximized = await this.electronService.isMaximized(); if (maximized) { @@ -421,10 +437,7 @@ export class TitlebarPart extends Part implements ITitleService { })); // Close - const closeIconContainer = append(this.windowControls, $('div.window-icon-bg')); - addClass(closeIconContainer, 'window-close-bg'); - const closeIcon = append(closeIconContainer, $('div.window-icon')); - addClass(closeIcon, 'window-close'); + const closeIcon = append(this.windowControls, $('div.window-icon.window-close.codicon.codicon-chrome-close')); this._register(addDisposableListener(closeIcon, EventType.CLICK, e => { this.electronService.closeWindow(); })); @@ -464,11 +477,11 @@ export class TitlebarPart extends Part implements ITitleService { private onDidChangeMaximized(maximized: boolean) { if (this.maxRestoreControl) { if (maximized) { - removeClass(this.maxRestoreControl, 'window-maximize'); - addClass(this.maxRestoreControl, 'window-unmaximize'); + removeClass(this.maxRestoreControl, 'codicon-chrome-maximize'); + addClass(this.maxRestoreControl, 'codicon-chrome-restore'); } else { - removeClass(this.maxRestoreControl, 'window-unmaximize'); - addClass(this.maxRestoreControl, 'window-maximize'); + removeClass(this.maxRestoreControl, 'codicon-chrome-restore'); + addClass(this.maxRestoreControl, 'codicon-chrome-maximize'); } } @@ -494,7 +507,7 @@ export class TitlebarPart extends Part implements ITitleService { removeClass(this.element, 'inactive'); } - const titleBackground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND); + const titleBackground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND) || ''; this.element.style.backgroundColor = titleBackground; if (titleBackground && Color.fromHex(titleBackground).isLighter()) { addClass(this.element, 'light'); @@ -506,15 +519,15 @@ export class TitlebarPart extends Part implements ITitleService { this.element.style.color = titleForeground; const titleBorder = this.getColor(TITLE_BAR_BORDER); - this.element.style.borderBottom = titleBorder ? `1px solid ${titleBorder}` : null; + this.element.style.borderBottom = titleBorder ? `1px solid ${titleBorder}` : ''; } } private onUpdateAppIconDragBehavior() { const setting = this.configurationService.getValue('window.doubleClickIconToClose'); - if (setting) { + if (setting && this.appIcon) { (this.appIcon.style as any)['-webkit-app-region'] = 'no-drag'; - } else { + } else if (this.appIcon) { (this.appIcon.style as any)['-webkit-app-region'] = 'drag'; } } @@ -546,8 +559,8 @@ export class TitlebarPart extends Part implements ITitleService { // 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.position = ''; + this.title.style.left = ''; this.title.style.transform = ''; return; } @@ -559,7 +572,7 @@ export class TitlebarPart extends Part implements ITitleService { } private get currentMenubarVisibility(): MenuBarVisibility { - return this.configurationService.getValue('window.menuBarVisibility'); + return getMenuBarVisibility(this.configurationService, this.environmentService); } updateLayout(dimension: Dimension): void { @@ -570,14 +583,24 @@ export class TitlebarPart extends Part implements ITitleService { if ((!isWeb && isMacintosh) || this.currentMenubarVisibility === 'hidden') { this.title.style.zoom = `${1 / getZoomFactor()}`; if (!isWeb && (isWindows || isLinux)) { - this.appIcon.style.zoom = `${1 / getZoomFactor()}`; - this.windowControls.style.zoom = `${1 / getZoomFactor()}`; + if (this.appIcon) { + this.appIcon.style.zoom = `${1 / getZoomFactor()}`; + } + + if (this.windowControls) { + this.windowControls.style.zoom = `${1 / getZoomFactor()}`; + } } } else { this.title.style.zoom = null; if (!isWeb && (isWindows || isLinux)) { - this.appIcon.style.zoom = null; - this.windowControls.style.zoom = null; + if (this.appIcon) { + this.appIcon.style.zoom = null; + } + + if (this.windowControls) { + this.windowControls.style.zoom = null; + } } } @@ -608,7 +631,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { if (titlebarActiveFg) { collector.addRule(` .monaco-workbench .part.titlebar > .window-controls-container .window-icon { - background-color: ${titlebarActiveFg}; + color: ${titlebarActiveFg}; } `); } @@ -617,7 +640,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { if (titlebarInactiveFg) { collector.addRule(` .monaco-workbench .part.titlebar.inactive > .window-controls-container .window-icon { - background-color: ${titlebarInactiveFg}; + color: ${titlebarInactiveFg}; } `); } diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 2629f6af73..b2616e10a6 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -32,7 +32,7 @@ import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; import { localize } from 'vs/nls'; import { timeout } from 'vs/base/common/async'; -import { editorFindMatchHighlight, editorFindMatchHighlightBorder, textLinkForeground, textCodeBlockBackground, focusBorder } from 'vs/platform/theme/common/colorRegistry'; +import { textLinkForeground, textCodeBlockBackground, focusBorder, listFilterMatchHighlight, listFilterMatchHighlightBorder } from 'vs/platform/theme/common/colorRegistry'; import { isString } from 'vs/base/common/types'; import { ILabelService } from 'vs/platform/label/common/label'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -673,15 +673,15 @@ class TreeDataSource implements IAsyncDataSource { // todo@joh,sandy make this proper and contributable from extensions registerThemingParticipant((theme, collector) => { - const findMatchHighlightColor = theme.getColor(editorFindMatchHighlight); - if (findMatchHighlightColor) { - collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; background-color: ${findMatchHighlightColor}; }`); - collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; background-color: ${findMatchHighlightColor}; }`); + const matchBackgroundColor = theme.getColor(listFilterMatchHighlight); + if (matchBackgroundColor) { + collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; background-color: ${matchBackgroundColor}; }`); + collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; background-color: ${matchBackgroundColor}; }`); } - const findMatchHighlightColorBorder = theme.getColor(editorFindMatchHighlightBorder); - if (findMatchHighlightColorBorder) { - collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${findMatchHighlightColorBorder}; box-sizing: border-box; }`); - collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${findMatchHighlightColorBorder}; box-sizing: border-box; }`); + const matchBorderColor = theme.getColor(listFilterMatchHighlightBorder); + if (matchBorderColor) { + collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${matchBorderColor}; box-sizing: border-box; }`); + collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${matchBorderColor}; box-sizing: border-box; }`); } const link = theme.getColor(textLinkForeground); if (link) { @@ -753,6 +753,23 @@ class TreeRenderer extends Disposable implements ITreeRenderer { + if ((Math.abs(start) > label.length) || (Math.abs(end) >= label.length)) { + return ({ start: 0, end: 0 }); + } + if (start < 0) { + start = label.length + start; + } + if (end < 0) { + end = label.length + end; + } + if (start > end) { + const swap = start; + start = end; + end = swap; + } + return ({ start, end }); + }) : undefined; const icon = this.themeService.getTheme().type === LIGHT ? node.icon : node.iconDark; const iconUrl = icon ? URI.revive(icon) : null; const title = node.tooltip ? node.tooltip : resource ? undefined : label; @@ -762,9 +779,9 @@ class TreeRenderer extends Disposable implements ITreeRenderer('explorer.decorations'); - templateData.resourceLabel.setResource({ name: label, description, resource: resource ? resource : URI.parse('missing:_icon_resource') }, { fileKind: this.getFileKind(node), title, hideIcon: !!iconUrl, fileDecorations, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: createMatches(element.filterData) }); + templateData.resourceLabel.setResource({ name: label, description, resource: resource ? resource : URI.parse('missing:_icon_resource') }, { fileKind: this.getFileKind(node), title, hideIcon: !!iconUrl, fileDecorations, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: matches ? matches : createMatches(element.filterData) }); } else { - templateData.resourceLabel.setResource({ name: label, description }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: createMatches(element.filterData) }); + templateData.resourceLabel.setResource({ name: label, description }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: matches ? matches : createMatches(element.filterData) }); } templateData.icon.style.backgroundImage = iconUrl ? DOM.asCSSUrl(iconUrl) : ''; diff --git a/src/vs/workbench/browser/parts/views/panelViewlet.ts b/src/vs/workbench/browser/parts/views/panelViewlet.ts index ee7e65153a..1d486b812c 100644 --- a/src/vs/workbench/browser/parts/views/panelViewlet.ts +++ b/src/vs/workbench/browser/parts/views/panelViewlet.ts @@ -29,6 +29,7 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IView, FocusedViewContext } from 'vs/workbench/common/views'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { assertIsDefined } from 'vs/base/common/types'; export interface IPanelColors extends IColorMapping { dropBackground?: ColorIdentifier; @@ -46,7 +47,7 @@ export interface IViewletPanelOptions extends IPanelOptions { export abstract class ViewletPanel extends Panel implements IView { - private static AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions'; + private static readonly AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions'; private _onDidFocus = this._register(new Emitter()); readonly onDidFocus: Event = this._onDidFocus.event; @@ -67,11 +68,11 @@ export abstract class ViewletPanel extends Panel implements IView { readonly title: string; protected actionRunner?: IActionRunner; - protected toolbar: ToolBar; + protected toolbar?: ToolBar; private readonly showActionsAlways: boolean = false; - private headerContainer: HTMLElement; - private titleContainer: HTMLElement; - protected twistiesContainer: HTMLElement; + private headerContainer?: HTMLElement; + private titleContainer?: HTMLElement; + protected twistiesContainer?: HTMLElement; constructor( options: IViewletPanelOptions, @@ -165,7 +166,9 @@ export abstract class ViewletPanel extends Panel implements IView { } protected updateTitle(title: string): void { - this.titleContainer.textContent = title; + if (this.titleContainer) { + this.titleContainer.textContent = title; + } this._onDidChangeTitleArea.fire(); } @@ -177,11 +180,16 @@ export abstract class ViewletPanel extends Panel implements IView { } private setActions(): void { - this.toolbar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions()))(); - this.toolbar.context = this.getActionsContext(); + if (this.toolbar) { + this.toolbar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions()))(); + this.toolbar.context = this.getActionsContext(); + } } private updateActionsVisibility(): void { + if (!this.headerContainer) { + return; + } const shouldAlwaysShowActions = this.configurationService.getValue('workbench.view.alwaysShowHeaderActions'); toggleClass(this.headerContainer, 'actions-always-visible', shouldAlwaysShowActions); } @@ -229,10 +237,10 @@ export class PanelViewlet extends Viewlet { private lastFocusedPanel: ViewletPanel | undefined; private panelItems: IViewletPanelItem[] = []; - private panelview: PanelView; + private panelview?: PanelView; get onDidSashChange(): Event { - return this.panelview.onDidSashChange; + return assertIsDefined(this.panelview).onDidSashChange; } protected get panels(): ViewletPanel[] { @@ -274,7 +282,7 @@ export class PanelViewlet extends Viewlet { event.stopPropagation(); event.preventDefault(); - let anchor: { x: number, y: number } = { x: event.posx, y: event.posy }; + let anchor: { x: number, y: number; } = { x: event.posx, y: event.posy }; this.contextMenuService.showContextMenu({ getAnchor: () => anchor, getActions: () => this.getContextMenuActions() @@ -332,7 +340,9 @@ export class PanelViewlet extends Viewlet { } layout(dimension: Dimension): void { - this.panelview.layout(dimension.height, dimension.width); + if (this.panelview) { + this.panelview.layout(dimension.height, dimension.width); + } } getOptimalWidth(): number { @@ -342,7 +352,7 @@ export class PanelViewlet extends Viewlet { return Math.max(...sizes); } - addPanels(panels: { panel: ViewletPanel, size: number, index?: number }[]): void { + addPanels(panels: { panel: ViewletPanel, size: number, index?: number; }[]): void { const wasSingleView = this.isSingleView(); for (const { panel, size, index } of panels) { @@ -378,7 +388,7 @@ export class PanelViewlet extends Viewlet { const panelItem: IViewletPanelItem = { panel, disposable }; this.panelItems.splice(index, 0, panelItem); - this.panelview.addPanel(panel, size, index); + assertIsDefined(this.panelview).addPanel(panel, size, index); } removePanels(panels: ViewletPanel[]): void { @@ -403,7 +413,7 @@ export class PanelViewlet extends Viewlet { this.lastFocusedPanel = undefined; } - this.panelview.removePanel(panel); + assertIsDefined(this.panelview).removePanel(panel); const [panelItem] = this.panelItems.splice(index, 1); panelItem.disposable.dispose(); @@ -424,15 +434,15 @@ export class PanelViewlet extends Viewlet { const [panelItem] = this.panelItems.splice(fromIndex, 1); this.panelItems.splice(toIndex, 0, panelItem); - this.panelview.movePanel(from, to); + assertIsDefined(this.panelview).movePanel(from, to); } resizePanel(panel: ViewletPanel, size: number): void { - this.panelview.resizePanel(panel, size); + assertIsDefined(this.panelview).resizePanel(panel, size); } getPanelSize(panel: ViewletPanel): number { - return this.panelview.getPanelSize(panel); + return assertIsDefined(this.panelview).getPanelSize(panel); } protected updateViewHeaders(): void { @@ -451,6 +461,8 @@ export class PanelViewlet extends Viewlet { dispose(): void { super.dispose(); this.panelItems.forEach(i => i.disposable.dispose()); - this.panelview.dispose(); + if (this.panelview) { + this.panelview.dispose(); + } } } diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index a1677c2483..00ac603e41 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/views'; -import { Disposable, IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IViewsService, IViewsViewlet, ViewContainer, IViewDescriptor, IViewContainersRegistry, Extensions as ViewExtensions, IView, IViewDescriptorCollection, IViewsRegistry } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -22,14 +22,14 @@ import { IFileIconTheme, IWorkbenchThemeService } from 'vs/workbench/services/th import { toggleClass, addClass } from 'vs/base/browser/dom'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -function filterViewRegisterEvent(container: ViewContainer, event: Event<{ viewContainer: ViewContainer, views: IViewDescriptor[] }>): Event { +function filterViewRegisterEvent(container: ViewContainer, event: Event<{ viewContainer: ViewContainer, views: IViewDescriptor[]; }>): Event { return Event.chain(event) .map(({ views, viewContainer }) => viewContainer === container ? views : []) .filter(views => views.length > 0) .event; } -function filterViewMoveEvent(container: ViewContainer, event: Event<{ from: ViewContainer, to: ViewContainer, views: IViewDescriptor[] }>): Event<{ added?: IViewDescriptor[], removed?: IViewDescriptor[] }> { +function filterViewMoveEvent(container: ViewContainer, event: Event<{ from: ViewContainer, to: ViewContainer, views: IViewDescriptor[]; }>): Event<{ added?: IViewDescriptor[], removed?: IViewDescriptor[]; }> { return Event.chain(event) .map(({ views, from, to }) => from === container ? { removed: views } : to === container ? { added: views } : {}) .filter(({ added, removed }) => isNonEmptyArray(added) || isNonEmptyArray(removed)) @@ -78,8 +78,8 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl private contextKeys = new CounterSet(); private items: IViewItem[] = []; - private _onDidChange: Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[] }> = this._register(new Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[] }>()); - readonly onDidChangeActiveViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[] }> = this._onDidChange.event; + private _onDidChange: Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._register(new Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }>()); + readonly onDidChangeActiveViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._onDidChange.event; get activeViewDescriptors(): IViewDescriptor[] { return this.items @@ -356,7 +356,7 @@ export class ContributableViewsModel extends Disposable { return viewDescriptor.workspace ? !!viewState.visibleWorkspace : !!viewState.visibleGlobal; } - private find(id: string): { index: number, visibleIndex: number, viewDescriptor: IViewDescriptor, state: IViewState } { + private find(id: string): { index: number, visibleIndex: number, viewDescriptor: IViewDescriptor, state: IViewState; } { for (let i = 0, visibleIndex = 0; i < this.viewDescriptors.length; i++) { const viewDescriptor = this.viewDescriptors[i]; const state = this.viewStates.get(viewDescriptor.id); @@ -436,8 +436,8 @@ export class ContributableViewsModel extends Disposable { (a, b) => a.id === b.id ? 0 : a.id < b.id ? -1 : 1 ).reverse(); - const toRemove: { index: number, viewDescriptor: IViewDescriptor }[] = []; - const toAdd: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean }[] = []; + const toRemove: { index: number, viewDescriptor: IViewDescriptor; }[] = []; + const toAdd: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean; }[] = []; for (const splice of splices) { const startViewDescriptor = this.viewDescriptors[splice.start]; @@ -521,7 +521,7 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { } private saveWorkspaceViewsStates(): void { - const storedViewsStates: { [id: string]: IStoredWorkspaceViewState } = JSON.parse(this.storageService.get(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}')); + const storedViewsStates: { [id: string]: IStoredWorkspaceViewState; } = JSON.parse(this.storageService.get(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}')); for (const viewDescriptor of this.viewDescriptors) { const viewState = this.viewStates.get(viewDescriptor.id); if (viewState) { @@ -557,7 +557,7 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { private static loadViewsStates(workspaceViewsStateStorageId: string, globalViewsStateStorageId: string, storageService: IStorageService): Map { const viewStates = new Map(); - const workspaceViewsStates = <{ [id: string]: IStoredWorkspaceViewState }>JSON.parse(storageService.get(workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}')); + const workspaceViewsStates = <{ [id: string]: IStoredWorkspaceViewState; }>JSON.parse(storageService.get(workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}')); for (const id of Object.keys(workspaceViewsStates)) { const workspaceViewState = workspaceViewsStates[id]; viewStates.set(id, { @@ -636,7 +636,7 @@ export class ViewsService extends Disposable implements IViewsService { _serviceBrand: undefined; - private readonly viewDescriptorCollections: Map; + private readonly viewDescriptorCollections: Map; private readonly viewDisposable: Map; private readonly activeViewContextKeys: Map>; @@ -646,7 +646,7 @@ export class ViewsService extends Disposable implements IViewsService { ) { super(); - this.viewDescriptorCollections = new Map(); + this.viewDescriptorCollections = new Map(); this.viewDisposable = new Map(); this.activeViewContextKeys = new Map>(); @@ -692,13 +692,13 @@ export class ViewsService extends Disposable implements IViewsService { } private onDidRegisterViewContainer(viewContainer: ViewContainer): void { - const viewDescriptorCollection = new ViewDescriptorCollection(viewContainer, this.contextKeyService); - const disposables: IDisposable[] = [viewDescriptorCollection]; + const disposables = new DisposableStore(); + const viewDescriptorCollection = disposables.add(new ViewDescriptorCollection(viewContainer, this.contextKeyService)); this.onDidChangeActiveViews({ added: viewDescriptorCollection.activeViewDescriptors, removed: [] }); viewDescriptorCollection.onDidChangeActiveViews(changed => this.onDidChangeActiveViews(changed), this, disposables); - this.viewDescriptorCollections.set(viewContainer, { viewDescriptorCollection, disposable: toDisposable(() => dispose(disposables)) }); + this.viewDescriptorCollections.set(viewContainer, { viewDescriptorCollection, disposable: disposables }); } private onDidDeregisterViewContainer(viewContainer: ViewContainer): void { @@ -709,7 +709,7 @@ export class ViewsService extends Disposable implements IViewsService { } } - private onDidChangeActiveViews({ added, removed }: { added: IViewDescriptor[], removed: IViewDescriptor[] }): void { + private onDidChangeActiveViews({ added, removed }: { added: IViewDescriptor[], removed: IViewDescriptor[]; }): void { added.forEach(viewDescriptor => this.getOrCreateActiveViewContextKey(viewDescriptor).set(true)); removed.forEach(viewDescriptor => this.getOrCreateActiveViewContextKey(viewDescriptor).set(false)); } @@ -717,7 +717,7 @@ export class ViewsService extends Disposable implements IViewsService { private onDidRegisterViews(container: ViewContainer, views: IViewDescriptor[]): void { const viewlet = this.viewletService.getViewlet(container.id); for (const viewDescriptor of views) { - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); const command: ICommandAction = { id: viewDescriptor.focusCommand ? viewDescriptor.focusCommand.id : `${viewDescriptor.id}.focus`, title: { original: `Focus on ${viewDescriptor.name} View`, value: localize('focus view', "Focus on {0} View", viewDescriptor.name) }, @@ -725,9 +725,9 @@ export class ViewsService extends Disposable implements IViewsService { }; const when = ContextKeyExpr.has(`${viewDescriptor.id}.active`); - disposables.push(CommandsRegistry.registerCommand(command.id, () => this.openView(viewDescriptor.id, true))); + disposables.add(CommandsRegistry.registerCommand(command.id, () => this.openView(viewDescriptor.id, true))); - disposables.push(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command, when })); @@ -745,7 +745,7 @@ export class ViewsService extends Disposable implements IViewsService { }); } - this.viewDisposable.set(viewDescriptor, toDisposable(() => dispose(disposables))); + this.viewDisposable.set(viewDescriptor, disposables); } } diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 1716a19464..71f94b18b5 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -64,6 +64,9 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView super(id, { showHeaderInTitleWhenSingleView, dnd: new DefaultPanelDndController() }, configurationService, layoutService, contextMenuService, telemetryService, themeService, storageService); const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).get(id); + if (!container) { + throw new Error('Could not find container'); + } this.viewsModel = this._register(this.instantiationService.createInstance(PersistentContributableViewsModel, container, viewletStateStorageId)); this.viewletState = this.getMemento(StorageScope.WORKSPACE); diff --git a/src/vs/workbench/browser/quickopen.ts b/src/vs/workbench/browser/quickopen.ts index a8b77354df..fc88a4a70f 100644 --- a/src/vs/workbench/browser/quickopen.ts +++ b/src/vs/workbench/browser/quickopen.ts @@ -3,11 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; -import * as objects from 'vs/base/common/objects'; -import * as arrays from 'vs/base/common/arrays'; -import * as strings from 'vs/base/common/strings'; -import * as types from 'vs/base/common/types'; +import { localize } from 'vs/nls'; +import { mixin, assign } from 'vs/base/common/objects'; +import { first } from 'vs/base/common/arrays'; +import { startsWith } from 'vs/base/common/strings'; +import { isString, assertIsDefined, withNullAsUndefined } from 'vs/base/common/types'; import { Registry } from 'vs/platform/registry/common/platform'; import { Action } from 'vs/base/common/actions'; import { Mode, IEntryRunContext, IAutoFocus, IModel, IQuickNavigateConfiguration } from 'vs/base/parts/quickopen/common/quickOpen'; @@ -111,9 +111,9 @@ export class QuickOpenHandler { */ getEmptyLabel(searchString: string): string { if (searchString.length > 0) { - return nls.localize('noResultsMatching', "No results matching"); + return localize('noResultsMatching', "No results matching"); } - return nls.localize('noResultsFound2', "No results found"); + return localize('noResultsFound2', "No results found"); } } @@ -128,9 +128,9 @@ export interface QuickOpenHandlerHelpEntry { */ export class QuickOpenHandlerDescriptor { prefix: string; - description: string; + description?: string; contextKey?: string; - helpEntries: QuickOpenHandlerHelpEntry[]; + helpEntries?: QuickOpenHandlerHelpEntry[]; instantProgress: boolean; private id: string; @@ -145,7 +145,7 @@ export class QuickOpenHandlerDescriptor { this.contextKey = contextKey; this.instantProgress = instantProgress; - if (types.isString(param)) { + if (isString(param)) { this.description = param; } else { this.helpEntries = param; @@ -195,7 +195,7 @@ export interface IQuickOpenRegistry { class QuickOpenRegistry implements IQuickOpenRegistry { private handlers: QuickOpenHandlerDescriptor[] = []; - private defaultHandler: QuickOpenHandlerDescriptor; + private defaultHandler: QuickOpenHandlerDescriptor | undefined; registerQuickOpenHandler(descriptor: QuickOpenHandlerDescriptor): void { this.handlers.push(descriptor); @@ -214,11 +214,11 @@ class QuickOpenRegistry implements IQuickOpenRegistry { } getQuickOpenHandler(text: string): QuickOpenHandlerDescriptor | null { - return text ? (arrays.first(this.handlers, h => strings.startsWith(text, h.prefix)) || null) : null; + return text ? (first(this.handlers, h => startsWith(text, h.prefix)) || null) : null; } getDefaultQuickOpenHandler(): QuickOpenHandlerDescriptor { - return this.defaultHandler; + return assertIsDefined(this.defaultHandler); } } @@ -275,17 +275,17 @@ export class EditorQuickOpenEntry extends QuickOpenEntry implements IEditorQuick if (input instanceof EditorInput) { let opts = this.getOptions(); if (opts) { - opts = objects.mixin(opts, openOptions, true); + opts = mixin(opts, openOptions, true); } else if (openOptions) { opts = EditorOptions.create(openOptions); } - this.editorService.openEditor(input, types.withNullAsUndefined(opts), sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + this.editorService.openEditor(input, withNullAsUndefined(opts), sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } else { const resourceInput = input; if (openOptions) { - resourceInput.options = objects.assign(resourceInput.options || Object.create(null), openOptions); + resourceInput.options = assign(resourceInput.options || Object.create(null), openOptions); } this.editorService.openEditor(resourceInput, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index a14931d8db..a8d1cd5971 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -21,6 +21,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IStorageService } from 'vs/platform/storage/common/storage'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; import { AbstractTree } from 'vs/base/browser/ui/tree/abstractTree'; +import { assertIsDefined } from 'vs/base/common/types'; export abstract class Viewlet extends Composite implements IViewlet { @@ -34,8 +35,8 @@ export abstract class Viewlet extends Composite implements IViewlet { super(id, telemetryService, themeService, storageService); } - getOptimalWidth(): number | null { - return null; + getOptimalWidth(): number | undefined { + return undefined; } getContextMenuActions(): IAction[] { @@ -76,7 +77,7 @@ export const Extensions = { }; export class ViewletRegistry extends CompositeRegistry { - private defaultViewletId!: string; + private defaultViewletId: string | undefined; /** * Registers a viewlet to the platform. @@ -120,7 +121,7 @@ export class ViewletRegistry extends CompositeRegistry { * Gets the id of the viewlet that should open on startup by default. */ getDefaultViewletId(): string { - return this.defaultViewletId; + return assertIsDefined(this.defaultViewletId); } } @@ -166,8 +167,9 @@ export class ShowViewletAction extends Action { private sidebarHasFocus(): boolean { const activeViewlet = this.viewletService.getActiveViewlet(); const activeElement = document.activeElement; + const sidebarPart = this.layoutService.getContainer(Parts.SIDEBAR_PART); - return !!(activeViewlet && activeElement && DOM.isAncestor(activeElement, this.layoutService.getContainer(Parts.SIDEBAR_PART))); + return !!(activeViewlet && activeElement && sidebarPart && DOM.isAncestor(activeElement, sidebarPart)); } } diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 428c08b4ee..258b4ed7fb 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -288,14 +288,6 @@ class BrowserMain extends Disposable { let workspace: IWorkspace | undefined = undefined; if (this.configuration.workspaceProvider) { workspace = this.configuration.workspaceProvider.workspace; - } else { - // TODO@ben remove me once IWorkspaceProvider API is adopted - const legacyConfiguration = this.configuration as { workspaceUri?: URI, folderUri?: URI }; - if (legacyConfiguration.workspaceUri) { - workspace = { workspaceUri: legacyConfiguration.workspaceUri }; - } else if (legacyConfiguration.folderUri) { - workspace = { folderUri: legacyConfiguration.folderUri }; - } } // Multi-root workspace @@ -311,7 +303,7 @@ class BrowserMain extends Disposable { return { id: 'empty-window' }; } - private getRemoteUserDataUri(): URI | null { + private getRemoteUserDataUri(): URI | undefined { const element = document.getElementById('vscode-remote-user-data-uri'); if (element) { const remoteUserDataPath = element.getAttribute('data-settings'); @@ -320,7 +312,7 @@ class BrowserMain extends Disposable { } } - return null; + return undefined; } } diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index c3ed8cdbd0..fadcfa0725 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -60,6 +60,16 @@ import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common ], 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'tabSizing' }, "Controls the sizing of editor tabs.") }, + 'workbench.editor.splitSizing': { + 'type': 'string', + 'enum': ['distribute', 'split'], + 'default': 'distribute', + 'enumDescriptions': [ + nls.localize('workbench.editor.splitSizingDistribute', "Splits all the editor groups to equal parts."), + nls.localize('workbench.editor.splitSizingSplit', "Splits the active editor group to equal parts.") + ], + 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'splitSizing' }, "Controls the sizing of editor groups when splitting them.") + }, 'workbench.editor.focusRecentEditorAfterClose': { 'type': 'boolean', 'description': nls.localize('focusRecentEditorAfterClose', "Controls whether tabs are closed in most recently used order or from left to right."), @@ -364,6 +374,11 @@ import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common 'type': 'boolean', 'default': false, 'description': nls.localize('zenMode.restore', "Controls whether a window should restore to zen mode if it was exited in zen mode.") + }, + 'zenMode.silentNotifications': { + 'type': 'boolean', + 'default': true, + 'description': nls.localize('zenMode.silentNotifications', "Controls whether notifications are shown while in zen mode. If true, only error notifications will pop out.") } } }); diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 9dae518c85..524f6b6ea1 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -19,7 +19,7 @@ import { IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/ import { IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs/workbench/browser/actions'; import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions'; import { Position, Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { IStorageService, WillSaveStateReason, StorageScope, IWillSaveStateEvent } from 'vs/platform/storage/common/storage'; +import { IStorageService, WillSaveStateReason, StorageScope } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; @@ -157,7 +157,7 @@ export class Workbench extends Layout { this.renderWorkbench(instantiationService, accessor.get(INotificationService) as NotificationService, storageService, configurationService); // Workbench Layout - this.createWorkbenchLayout(instantiationService); + this.createWorkbenchLayout(); // Layout this.layout(); @@ -227,6 +227,20 @@ export class Workbench extends Layout { configurationService: IConfigurationService ): void { + // Configuration changes + this._register(configurationService.onDidChangeConfiguration(() => this.setFontAliasing(configurationService))); + + // Font Info + if (isNative) { + this._register(storageService.onWillSaveState(e => { + if (e.reason === WillSaveStateReason.SHUTDOWN) { + this.storeFontInfo(storageService); + } + })); + } else { + this._register(lifecycleService.onWillShutdown(() => this.storeFontInfo(storageService))); + } + // Lifecycle this._register(lifecycleService.onBeforeShutdown(event => this._onBeforeShutdown.fire(event))); this._register(lifecycleService.onWillShutdown(event => this._onWillShutdown.fire(event))); @@ -234,12 +248,6 @@ export class Workbench extends Layout { this._onShutdown.fire(); this.dispose(); })); - - // Configuration changes - this._register(configurationService.onDidChangeConfiguration(() => this.setFontAliasing(configurationService))); - - // Storage - this._register(storageService.onWillSaveState(e => this.storeFontInfo(e, storageService))); } private fontAliasing: 'default' | 'antialiased' | 'none' | 'auto' | undefined; @@ -279,13 +287,19 @@ export class Workbench extends Layout { readFontInfo(BareFontInfo.createFromRawSettings(configurationService.getValue('editor'), getZoomLevel())); } - private storeFontInfo(e: IWillSaveStateEvent, storageService: IStorageService): void { - if (e.reason === WillSaveStateReason.SHUTDOWN) { - const serializedFontInfo = serializeFontInfo(); - if (serializedFontInfo) { - const serializedFontInfoRaw = JSON.stringify(serializedFontInfo); + private storeFontInfo(storageService: IStorageService): void { + const serializedFontInfo = serializeFontInfo(); + if (serializedFontInfo) { + const serializedFontInfoRaw = JSON.stringify(serializedFontInfo); - isNative ? storageService.store('editorFontInfo', serializedFontInfoRaw, StorageScope.GLOBAL) : window.localStorage.setItem('editorFontInfo', serializedFontInfoRaw); + // Font info is very specific to the machine the workbench runs + // on. As such, in the web, we prefer to store this info in + // local storage and not global storage because it would not make + // much sense to synchronize to other machines. + if (isNative) { + storageService.store('editorFontInfo', serializedFontInfoRaw, StorageScope.GLOBAL); + } else { + window.localStorage.setItem('editorFontInfo', serializedFontInfoRaw); } } } diff --git a/src/vs/workbench/common/actions.ts b/src/vs/workbench/common/actions.ts index 51eb00557b..232937b1a3 100644 --- a/src/vs/workbench/common/actions.ts +++ b/src/vs/workbench/common/actions.ts @@ -46,10 +46,10 @@ Registry.add(Extensions.WorkbenchActions, new class implements IWorkbenchActionR weight: weight, when: (descriptor.keybindingContext || when ? ContextKeyExpr.and(descriptor.keybindingContext, when) : null), primary: keybindings ? keybindings.primary : 0, - secondary: keybindings && keybindings.secondary, - win: keybindings && keybindings.win, - mac: keybindings && keybindings.mac, - linux: keybindings && keybindings.linux + secondary: keybindings?.secondary, + win: keybindings?.win, + mac: keybindings?.mac, + linux: keybindings?.linux }); // menu item @@ -112,7 +112,7 @@ Registry.add(Extensions.WorkbenchActions, new class implements IWorkbenchActionR // otherwise run and dispose try { - const from = args && args.from || 'keybinding'; + const from = args?.from || 'keybinding'; await actionInstance.run(undefined, { from }); } finally { actionInstance.dispose(); diff --git a/src/vs/workbench/common/contributions.ts b/src/vs/workbench/common/contributions.ts index 6c94b08bf1..db1197674f 100644 --- a/src/vs/workbench/common/contributions.ts +++ b/src/vs/workbench/common/contributions.ts @@ -63,11 +63,11 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry } start(accessor: ServicesAccessor): void { - this.instantiationService = accessor.get(IInstantiationService); - this.lifecycleService = accessor.get(ILifecycleService); + const instantiationService = this.instantiationService = accessor.get(IInstantiationService); + const lifecycleService = this.lifecycleService = accessor.get(ILifecycleService); [LifecyclePhase.Starting, LifecyclePhase.Ready, LifecyclePhase.Restored, LifecyclePhase.Eventually].forEach(phase => { - this.instantiateByPhase(this.instantiationService!, this.lifecycleService!, phase); + this.instantiateByPhase(instantiationService, lifecycleService, phase); }); } @@ -119,7 +119,7 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry try { instantiationService.createInstance(ctor); } catch (error) { - console.error(`Unable to instantiate workbench contribution ${ctor}.`, error); + console.error(`Unable to instantiate workbench contribution ${(ctor as any).name}.`, error); } } } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 758e138bc4..da8a85f0be 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -5,11 +5,11 @@ import { Event, Emitter } from 'vs/base/common/event'; import { assign } from 'vs/base/common/objects'; -import { isUndefinedOrNull } from 'vs/base/common/types'; +import { isUndefinedOrNull, withNullAsUndefined, assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { IEditor as ICodeEditor, IEditorViewState, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; -import { IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceInput, IResourceInput, EditorActivation } from 'vs/platform/editor/common/editor'; +import { IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceInput, IResourceInput, EditorActivation, EditorOpenContext } from 'vs/platform/editor/common/editor'; import { IInstantiationService, IConstructorSignature0, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -38,6 +38,7 @@ 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); +export const EditorAreaVisibleContext = new RawContextKey('editorAreaVisible', true); /** * Text diff editor id. @@ -117,7 +118,7 @@ export interface ITextEditor extends IEditor { /** * Returns the underlying text editor widget of this editor. */ - getControl(): ICodeEditor; + getControl(): ICodeEditor | undefined; } export interface ITextDiffEditor extends IEditor { @@ -125,7 +126,7 @@ export interface ITextDiffEditor extends IEditor { /** * Returns the underlying text editor widget of this editor. */ - getControl(): IDiffEditor; + getControl(): IDiffEditor | undefined; } export interface ITextSideBySideEditor extends IEditor { @@ -510,7 +511,7 @@ export interface IEncodingSupport { /** * Gets the encoding of the input if known. */ - getEncoding(): string; + getEncoding(): string | undefined; /** * Sets the encoding for the input for saving. @@ -786,6 +787,18 @@ export class EditorOptions implements IEditorOptions { */ ignoreOverrides: boolean | undefined; + /** + * A optional hint to signal in which context the editor opens. + * + * If configured to be `EditorOpenContext.USER`, this hint can be + * used in various places to control the experience. For example, + * if the editor to open fails with an error, a notification could + * inform about this in a modal dialog. If the editor opened through + * some background task, the notification would show in the background, + * not as a modal dialog. + */ + context: EditorOpenContext | undefined; + /** * Overwrites option values from the provided bag. */ @@ -830,6 +843,10 @@ export class EditorOptions implements IEditorOptions { this.ignoreOverrides = options.ignoreOverrides; } + if (typeof options.context === 'number') { + this.context = options.context; + } + return this; } } @@ -838,13 +855,13 @@ export class EditorOptions implements IEditorOptions { * Base Text Editor Options. */ export class TextEditorOptions extends EditorOptions { - private startLineNumber: number; - private startColumn: number; - private endLineNumber: number; - private endColumn: number; + private startLineNumber: number | undefined; + private startColumn: number | undefined; + private endLineNumber: number | undefined; + private endColumn: number | undefined; - private revealInCenterIfOutsideViewport: boolean; - private editorViewState: IEditorViewState | null; + private revealInCenterIfOutsideViewport: boolean | undefined; + private editorViewState: IEditorViewState | undefined; static from(input?: IBaseResourceInput): TextEditorOptions | undefined { if (!input || !input.options) { @@ -912,7 +929,7 @@ export class TextEditorOptions extends EditorOptions { const options = TextEditorOptions.create(settings); // View state - options.editorViewState = editor.saveViewState(); + options.editorViewState = withNullAsUndefined(editor.saveViewState()); return options; } @@ -1036,6 +1053,7 @@ interface IEditorPartConfiguration { mouseBackForwardToNavigate?: boolean; labelFormat?: 'default' | 'short' | 'medium' | 'long'; restoreViewState?: boolean; + splitSizing?: 'split' | 'distribute'; } export interface IEditorPartOptions extends IEditorPartConfiguration { @@ -1057,7 +1075,7 @@ export function toResource(editor: IEditorInput | undefined, options?: IResource return undefined; } - if (options && options.supportSideBySide && editor instanceof SideBySideEditorInput) { + if (options?.supportSideBySide && editor instanceof SideBySideEditorInput) { editor = options.supportSideBySide === SideBySideEditor.MASTER ? editor.master : editor.details; } @@ -1095,23 +1113,23 @@ export interface IEditorMemento { } class EditorInputFactoryRegistry implements IEditorInputFactoryRegistry { - private instantiationService: IInstantiationService; - private fileInputFactory: IFileInputFactory; + private instantiationService: IInstantiationService | undefined; + private fileInputFactory: IFileInputFactory | undefined; private readonly editorInputFactoryConstructors: Map> = new Map(); private readonly editorInputFactoryInstances: Map = new Map(); start(accessor: ServicesAccessor): void { - this.instantiationService = accessor.get(IInstantiationService); + const instantiationService = this.instantiationService = accessor.get(IInstantiationService); this.editorInputFactoryConstructors.forEach((ctor, key) => { - this.createEditorInputFactory(key, ctor); + this.createEditorInputFactory(key, ctor, instantiationService); }); this.editorInputFactoryConstructors.clear(); } - private createEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0): void { - const instance = this.instantiationService.createInstance(ctor); + private createEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0, instantiationService: IInstantiationService): void { + const instance = instantiationService.createInstance(ctor); this.editorInputFactoryInstances.set(editorInputId, instance); } @@ -1120,14 +1138,14 @@ class EditorInputFactoryRegistry implements IEditorInputFactoryRegistry { } getFileInputFactory(): IFileInputFactory { - return this.fileInputFactory; + return assertIsDefined(this.fileInputFactory); } registerEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0): void { if (!this.instantiationService) { this.editorInputFactoryConstructors.set(editorInputId, ctor); } else { - this.createEditorInputFactory(editorInputId, ctor); + this.createEditorInputFactory(editorInputId, ctor, this.instantiationService); } } diff --git a/src/vs/workbench/common/editor/binaryEditorModel.ts b/src/vs/workbench/common/editor/binaryEditorModel.ts index e139c27d59..2f55727b35 100644 --- a/src/vs/workbench/common/editor/binaryEditorModel.ts +++ b/src/vs/workbench/common/editor/binaryEditorModel.ts @@ -7,25 +7,27 @@ import { EditorModel } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { Schemas } from 'vs/base/common/network'; -import { DataUri } from 'vs/base/common/resources'; +import { DataUri, basename } from 'vs/base/common/resources'; +import { MIME_BINARY } from 'vs/base/common/mime'; /** * An editor model that just represents a resource that can be loaded. */ export class BinaryEditorModel extends EditorModel { - private size: number; + private size: number | undefined; private etag: string | undefined; private readonly mime: string; constructor( private readonly resource: URI, - private readonly name: string, + private readonly name: string | undefined, @IFileService private readonly fileService: IFileService ) { super(); this.resource = resource; this.name = name; + this.mime = MIME_BINARY; if (resource.scheme === Schemas.data) { const metadata = DataUri.parseMetaData(resource); @@ -33,7 +35,10 @@ export class BinaryEditorModel extends EditorModel { this.size = Number(metadata.get(DataUri.META_DATA_SIZE)); } - this.mime = metadata.get(DataUri.META_DATA_MIME)!; + const metadataMime = metadata.get(DataUri.META_DATA_MIME); + if (metadataMime) { + this.mime = metadataMime; + } } } @@ -41,7 +46,7 @@ export class BinaryEditorModel extends EditorModel { * The name of the binary resource. */ getName(): string { - return this.name; + return this.name || basename(this.resource); } /** @@ -54,7 +59,7 @@ export class BinaryEditorModel extends EditorModel { /** * The size of the binary resource if known. */ - getSize(): number { + getSize(): number | undefined { return this.size; } diff --git a/src/vs/workbench/common/editor/diffEditorModel.ts b/src/vs/workbench/common/editor/diffEditorModel.ts index 53e73c1501..38d3cf72ca 100644 --- a/src/vs/workbench/common/editor/diffEditorModel.ts +++ b/src/vs/workbench/common/editor/diffEditorModel.ts @@ -11,6 +11,7 @@ import { IEditorModel } from 'vs/platform/editor/common/editor'; * and the modified version. */ export class DiffEditorModel extends EditorModel { + protected readonly _originalModel: IEditorModel | null; protected readonly _modifiedModel: IEditorModel | null; @@ -58,4 +59,4 @@ export class DiffEditorModel extends EditorModel { super.dispose(); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/common/editor/editorGroup.ts b/src/vs/workbench/common/editor/editorGroup.ts index e1c2d7a98a..850c372210 100644 --- a/src/vs/workbench/common/editor/editorGroup.ts +++ b/src/vs/workbench/common/editor/editorGroup.ts @@ -109,7 +109,7 @@ export class EditorGroup extends Disposable { private focusRecentEditorAfterClose: boolean | undefined; constructor( - labelOrSerializedGroup: ISerializedEditorGroup, + labelOrSerializedGroup: ISerializedEditorGroup | undefined, @IInstantiationService private readonly instantiationService: IInstantiationService, @IConfigurationService private readonly configurationService: IConfigurationService ) { @@ -156,7 +156,7 @@ export class EditorGroup extends Disposable { for (const editor of this.editors) { const editorResource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); - if (editorResource && editorResource.toString() === resource.toString()) { + if (editorResource?.toString() === resource.toString()) { return editor; } } @@ -183,8 +183,8 @@ export class EditorGroup extends Disposable { openEditor(editor: EditorInput, options?: IEditorOpenOptions): void { const index = this.indexOf(editor); - const makePinned = options && options.pinned; - const makeActive = (options && options.active) || !this.activeEditor || (!makePinned && this.matches(this.preview, this.activeEditor)); + const makePinned = options?.pinned; + const makeActive = options?.active || !this.activeEditor || (!makePinned && this.matches(this.preview, this.activeEditor)); // New editor if (index === -1) { diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index 6064e31cba..0f5f948680 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -8,6 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { IReference } from 'vs/base/common/lifecycle'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; +import { basename } from 'vs/base/common/resources'; /** * A read-only text editor input whos contents are made of the provided resource that points to an existing @@ -21,7 +22,7 @@ export class ResourceEditorInput extends EditorInput implements IModeSupport { private modelReference: Promise> | null = null; constructor( - private name: string, + private name: string | undefined, private description: string | undefined, private readonly resource: URI, private preferredMode: string | undefined, @@ -43,7 +44,7 @@ export class ResourceEditorInput extends EditorInput implements IModeSupport { } getName(): string { - return this.name; + return this.name || basename(this.resource); } setName(name: string): void { diff --git a/src/vs/workbench/common/editor/textDiffEditorModel.ts b/src/vs/workbench/common/editor/textDiffEditorModel.ts index e49d9b5365..0960337ad4 100644 --- a/src/vs/workbench/common/editor/textDiffEditorModel.ts +++ b/src/vs/workbench/common/editor/textDiffEditorModel.ts @@ -14,14 +14,17 @@ import { DiffEditorModel } from 'vs/workbench/common/editor/diffEditorModel'; */ export class TextDiffEditorModel extends DiffEditorModel { - protected readonly _originalModel!: BaseTextEditorModel | null; - protected readonly _modifiedModel!: BaseTextEditorModel | null; + protected readonly _originalModel: BaseTextEditorModel | null; + protected readonly _modifiedModel: BaseTextEditorModel | null; private _textDiffEditorModel: IDiffEditorModel | null = null; constructor(originalModel: BaseTextEditorModel, modifiedModel: BaseTextEditorModel) { super(originalModel, modifiedModel); + this._originalModel = originalModel; + this._modifiedModel = modifiedModel; + this.updateTextDiffEditorModel(); } @@ -42,7 +45,7 @@ export class TextDiffEditorModel extends DiffEditorModel { } private updateTextDiffEditorModel(): void { - if (this.originalModel && this.originalModel.isResolved() && this.modifiedModel && this.modifiedModel.isResolved()) { + if (this.originalModel?.isResolved() && this.modifiedModel?.isResolved()) { // Create new if (!this._textDiffEditorModel) { diff --git a/src/vs/workbench/common/editor/untitledEditorInput.ts b/src/vs/workbench/common/editor/untitledEditorInput.ts index d7229b2cc5..2e743932e1 100644 --- a/src/vs/workbench/common/editor/untitledEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledEditorInput.ts @@ -35,9 +35,9 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport constructor( private readonly resource: URI, private readonly _hasAssociatedFilePath: boolean, - private preferredMode: string, - private readonly initialValue: string, - private preferredEncoding: string, + private preferredMode: string | undefined, + private readonly initialValue: string | undefined, + private preferredEncoding: string | undefined, @IInstantiationService private readonly instantiationService: IInstantiationService, @ITextFileService private readonly textFileService: ITextFileService, @ILabelService private readonly labelService: ILabelService @@ -182,7 +182,7 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport return this.getName(); } - getEncoding(): string { + getEncoding(): string | undefined { if (this.cachedModel) { return this.cachedModel.getEncoding(); } diff --git a/src/vs/workbench/common/editor/untitledEditorModel.ts b/src/vs/workbench/common/editor/untitledEditorModel.ts index c6488f4e4e..886b32a3eb 100644 --- a/src/vs/workbench/common/editor/untitledEditorModel.ts +++ b/src/vs/workbench/common/editor/untitledEditorModel.ts @@ -33,14 +33,14 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin private dirty: boolean = false; private versionId: number = 0; private readonly contentChangeEventScheduler: RunOnceScheduler; - private configuredEncoding: string; + private configuredEncoding?: string; constructor( - private readonly preferredMode: string, + private readonly preferredMode: string | undefined, private readonly resource: URI, private _hasAssociatedFilePath: boolean, - private readonly initialValue: string, - private preferredEncoding: string, + private readonly initialValue: string | undefined, + private preferredEncoding: string | undefined, @IModeService modeService: IModeService, @IModelService modelService: IModelService, @IBackupFileService private readonly backupFileService: IBackupFileService, @@ -87,7 +87,7 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin return this.preferredMode; } - getEncoding(): string { + getEncoding(): string | undefined { return this.preferredEncoding || this.configuredEncoding; } @@ -192,7 +192,7 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin // mark the untitled editor as non-dirty once its content becomes empty and we do // not have an associated path set. we never want dirty indicator in that case. - if (!this._hasAssociatedFilePath && this.textEditorModel && this.textEditorModel.getLineCount() === 1 && this.textEditorModel.getLineContent(1) === '') { + if (!this._hasAssociatedFilePath && this.textEditorModel.getLineCount() === 1 && this.textEditorModel.getLineContent(1) === '') { this.setDirty(false); } diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index a5d74afc5c..b157a20656 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { INotification, INotificationHandle, INotificationActions, INotificationProgress, NoOpNotification, Severity, NotificationMessage, IPromptChoice, IStatusMessageOptions } from 'vs/platform/notification/common/notification'; +import { INotification, INotificationHandle, INotificationActions, INotificationProgress, NoOpNotification, Severity, NotificationMessage, IPromptChoice, IStatusMessageOptions, NotificationsFilter } from 'vs/platform/notification/common/notification'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -12,6 +12,7 @@ import { Action } from 'vs/base/common/actions'; import { isErrorWithActions } from 'vs/base/common/errorsWithActions'; import { startsWith } from 'vs/base/common/strings'; import { localize } from 'vs/nls'; +import { find, equals } from 'vs/base/common/arrays'; export interface INotificationsModel { @@ -22,9 +23,12 @@ export interface INotificationsModel { readonly notifications: INotificationViewItem[]; readonly onDidNotificationChange: Event; + readonly onDidFilterChange: Event; addNotification(notification: INotification): INotificationHandle; + setFilter(filter: NotificationsFilter): void; + // // Notifications as Status // @@ -123,20 +127,31 @@ export class NotificationHandle implements INotificationHandle { export class NotificationsModel extends Disposable implements INotificationsModel { - private static NO_OP_NOTIFICATION = new NoOpNotification(); + private static readonly NO_OP_NOTIFICATION = new NoOpNotification(); - private readonly _onDidNotificationChange: Emitter = this._register(new Emitter()); + private readonly _onDidNotificationChange = this._register(new Emitter()); readonly onDidNotificationChange: Event = this._onDidNotificationChange.event; - private readonly _onDidStatusMessageChange: Emitter = this._register(new Emitter()); + private readonly _onDidStatusMessageChange = this._register(new Emitter()); readonly onDidStatusMessageChange: Event = this._onDidStatusMessageChange.event; + private readonly _onDidFilterChange = this._register(new Emitter()); + readonly onDidFilterChange: Event = this._onDidFilterChange.event; + private readonly _notifications: INotificationViewItem[] = []; get notifications(): INotificationViewItem[] { return this._notifications; } private _statusMessage: IStatusMessageViewItem | undefined; get statusMessage(): IStatusMessageViewItem | undefined { return this._statusMessage; } + private filter = NotificationsFilter.OFF; + + setFilter(filter: NotificationsFilter): void { + this.filter = filter; + + this._onDidFilterChange.fire(filter); + } + addNotification(notification: INotification): INotificationHandle { const item = this.createViewItem(notification); if (!item) { @@ -169,19 +184,13 @@ export class NotificationsModel extends Disposable implements INotificationsMode } private findNotification(item: INotificationViewItem): INotificationViewItem | undefined { - for (const notification of this._notifications) { - if (notification.equals(item)) { - return notification; - } - } - - return undefined; + return find(this._notifications, notification => notification.equals(item)); } - private createViewItem(notification: INotification): INotificationViewItem | null { - const item = NotificationViewItem.create(notification); + private createViewItem(notification: INotification): INotificationViewItem | undefined { + const item = NotificationViewItem.create(notification, this.filter); if (!item) { - return null; + return undefined; } // Item Events @@ -385,11 +394,11 @@ export interface INotificationMessage { export class NotificationViewItem extends Disposable implements INotificationViewItem { - private static MAX_MESSAGE_LENGTH = 1000; + private static readonly MAX_MESSAGE_LENGTH = 1000; // Example link: "Some message with [link text](http://link.href)." // RegEx: [, anything not ], ], (, http://|https://|command:, no whitespace) - private static LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:)[^\)\s]+)(?: "([^"]+)")?\)/gi; + private static readonly LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:)[^\)\s]+)(?: "([^"]+)")?\)/gi; private _expanded: boolean | undefined; @@ -405,9 +414,9 @@ export class NotificationViewItem extends Disposable implements INotificationVie private readonly _onDidLabelChange: Emitter = this._register(new Emitter()); readonly onDidLabelChange: Event = this._onDidLabelChange.event; - static create(notification: INotification): INotificationViewItem | null { + static create(notification: INotification, filter: NotificationsFilter = NotificationsFilter.OFF): INotificationViewItem | undefined { if (!notification || !notification.message || isPromiseCanceledError(notification.message)) { - return null; // we need a message to show + return undefined; // we need a message to show } let severity: Severity; @@ -419,7 +428,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie const message = NotificationViewItem.parseNotificationMessage(notification.message); if (!message) { - return null; // we need a message to show + return undefined; // we need a message to show } let actions: INotificationActions | undefined; @@ -429,7 +438,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie actions = { primary: notification.message.actions }; } - return new NotificationViewItem(severity, notification.sticky, notification.silent, message, notification.source, actions); + return new NotificationViewItem(severity, notification.sticky, notification.silent || filter === NotificationsFilter.SILENT || (filter === NotificationsFilter.ERROR && notification.severity !== Severity.Error), message, notification.source, actions); } private static parseNotificationMessage(input: NotificationMessage): INotificationMessage | undefined { @@ -641,17 +650,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie const primaryActions = (this._actions && this._actions.primary) || []; const otherPrimaryActions = (other.actions && other.actions.primary) || []; - if (primaryActions.length !== otherPrimaryActions.length) { - return false; - } - - for (let i = 0; i < primaryActions.length; i++) { - if ((primaryActions[i].id + primaryActions[i].label) !== (otherPrimaryActions[i].id + otherPrimaryActions[i].label)) { - return false; - } - } - - return true; + return equals(primaryActions, otherPrimaryActions, (a, b) => (a.id + a.label) === (b.id + b.label)); } } @@ -690,9 +689,9 @@ export class ChoiceAction extends Action { class StatusMessageViewItem { - static create(notification: NotificationMessage, options?: IStatusMessageOptions): IStatusMessageViewItem | null { + static create(notification: NotificationMessage, options?: IStatusMessageOptions): IStatusMessageViewItem | undefined { if (!notification || isPromiseCanceledError(notification)) { - return null; // we need a message to show + return undefined; // we need a message to show } let message: string | undefined; @@ -703,7 +702,7 @@ class StatusMessageViewItem { } if (!message) { - return null; // we need a message to show + return undefined; // we need a message to show } return { message, options }; diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts index d133e221d9..5bd454a9e3 100644 --- a/src/vs/workbench/common/resources.ts +++ b/src/vs/workbench/common/resources.ts @@ -18,13 +18,13 @@ import { withNullAsUndefined } from 'vs/base/common/types'; export class ResourceContextKey extends Disposable implements IContextKey { - static Scheme = new RawContextKey('resourceScheme', undefined); - static Filename = new RawContextKey('resourceFilename', undefined); - static LangId = new RawContextKey('resourceLangId', undefined); - static Resource = new RawContextKey('resource', undefined); - static Extension = new RawContextKey('resourceExtname', undefined); - static HasResource = new RawContextKey('resourceSet', false); - static IsFileSystemResource = new RawContextKey('isFileSystemResource', false); + static readonly Scheme = new RawContextKey('resourceScheme', undefined); + static readonly Filename = new RawContextKey('resourceFilename', undefined); + static readonly LangId = new RawContextKey('resourceLangId', undefined); + static readonly Resource = new RawContextKey('resource', undefined); + static readonly Extension = new RawContextKey('resourceExtname', undefined); + static readonly HasResource = new RawContextKey('resourceSet', false); + static readonly IsFileSystemResource = new RawContextKey('isFileSystemResource', false); private readonly _resourceKey: IContextKey; private readonly _schemeKey: IContextKey; @@ -180,24 +180,24 @@ export class ResourceGlobMatcher extends Disposable { matches(resource: URI): boolean { const folder = this.contextService.getWorkspaceFolder(resource); - let expressionForRoot: ParsedExpression; + let expressionForRoot: ParsedExpression | undefined; if (folder && this.mapRootToParsedExpression.has(folder.uri.toString())) { - expressionForRoot = this.mapRootToParsedExpression.get(folder.uri.toString())!; + expressionForRoot = this.mapRootToParsedExpression.get(folder.uri.toString()); } else { - expressionForRoot = this.mapRootToParsedExpression.get(ResourceGlobMatcher.NO_ROOT)!; + expressionForRoot = this.mapRootToParsedExpression.get(ResourceGlobMatcher.NO_ROOT); } // If the resource if from a workspace, convert its absolute path to a relative // path so that glob patterns have a higher probability to match. For example // a glob pattern of "src/**" will not match on an absolute path "/folder/src/file.txt" // but can match on "src/file.txt" - let resourcePathToMatch: string; + let resourcePathToMatch: string | undefined; if (folder) { - resourcePathToMatch = relativePath(folder.uri, resource)!; // always uses forward slashes + resourcePathToMatch = relativePath(folder.uri, resource); // always uses forward slashes } else { resourcePathToMatch = resource.fsPath; // TODO@isidor: support non-file URIs } - return !!expressionForRoot(resourcePathToMatch); + return !!expressionForRoot && typeof resourcePathToMatch === 'string' && !!expressionForRoot(resourcePathToMatch); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 093aea3ae8..3e0871eb4a 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { registerColor, editorBackground, contrastBorder, transparent, editorWidgetBackground, textLinkForeground, lighten, darken, focusBorder, activeContrastBorder, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry'; +import { registerColor, editorBackground, contrastBorder, transparent, editorWidgetBackground, textLinkForeground, lighten, darken, focusBorder, activeContrastBorder, editorWidgetForeground, editorErrorForeground, editorWarningForeground, editorInfoForeground } from 'vs/platform/theme/common/colorRegistry'; import { Disposable } from 'vs/base/common/lifecycle'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; @@ -235,8 +235,8 @@ export const PANEL_INACTIVE_TITLE_FOREGROUND = registerColor('panelTitle.inactiv }, nls.localize('panelInactiveTitleForeground', "Title color for the inactive panel. Panels are shown below the editor area and contain views like output and integrated terminal.")); export const PANEL_ACTIVE_TITLE_BORDER = registerColor('panelTitle.activeBorder', { - dark: PANEL_BORDER, - light: PANEL_BORDER, + dark: PANEL_ACTIVE_TITLE_FOREGROUND, + light: PANEL_ACTIVE_TITLE_FOREGROUND, hc: contrastBorder }, nls.localize('panelActiveTitleBorder', "Border color for the active panel title. Panels are shown below the editor area and contain views like output and integrated terminal.")); @@ -335,8 +335,8 @@ export const ACTIVITY_BAR_FOREGROUND = registerColor('activityBar.foreground', { }, nls.localize('activityBarForeground', "Activity bar item foreground color when it is active. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); export const ACTIVITY_BAR_INACTIVE_FOREGROUND = registerColor('activityBar.inactiveForeground', { - dark: transparent(ACTIVITY_BAR_FOREGROUND, 0.6), - light: transparent(ACTIVITY_BAR_FOREGROUND, 0.6), + dark: transparent(ACTIVITY_BAR_FOREGROUND, 0.4), + light: transparent(ACTIVITY_BAR_FOREGROUND, 0.4), hc: Color.white }, nls.localize('activityBarInActiveForeground', "Activity bar item foreground color when it is inactive. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); @@ -346,6 +346,18 @@ export const ACTIVITY_BAR_BORDER = registerColor('activityBar.border', { hc: contrastBorder }, nls.localize('activityBarBorder', "Activity bar border color separating to the side bar. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); +export const ACTIVITY_BAR_ACTIVE_BORDER = registerColor('activityBar.activeBorder', { + dark: ACTIVITY_BAR_FOREGROUND, + light: ACTIVITY_BAR_FOREGROUND, + hc: null +}, nls.localize('activityBarActiveBorder', "Activity bar border color for the active item. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); + +export const ACTIVITY_BAR_ACTIVE_BACKGROUND = registerColor('activityBar.activeBackground', { + dark: null, + light: null, + hc: null +}, nls.localize('activityBarActiveBackground', "Activity bar background color for the active item. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); + export const ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND = registerColor('activityBar.dropBackground', { dark: Color.white.transparent(0.12), light: Color.white.transparent(0.12), @@ -560,21 +572,21 @@ export const NOTIFICATIONS_BORDER = registerColor('notifications.border', { }, nls.localize('notificationsBorder', "Notifications border color separating from other notifications in the notifications center. Notifications slide in from the bottom right of the window.")); export const NOTIFICATIONS_ERROR_ICON_FOREGROUND = registerColor('notificationsErrorIcon.foreground', { - dark: '#F48771', - light: '#A1260D', - hc: '#F48771' + dark: editorErrorForeground, + light: editorErrorForeground, + hc: editorErrorForeground }, nls.localize('notificationsErrorIconForeground', "The color used for the notification error icon.")); export const NOTIFICATIONS_WARNING_ICON_FOREGROUND = registerColor('notificationsWarningIcon.foreground', { - dark: '#FFCC00', - light: '#DDB100', - hc: '#FFCC00' + dark: editorWarningForeground, + light: editorWarningForeground, + hc: editorWarningForeground }, nls.localize('notificationsWarningIconForeground', "The color used for the notification warning icon.")); export const NOTIFICATIONS_INFO_ICON_FOREGROUND = registerColor('notificationsInfoIcon.foreground', { - dark: '#75BEFF', - light: '#007ACC', - hc: '#75BEFF' + dark: editorInfoForeground, + light: editorInfoForeground, + hc: editorInfoForeground }, nls.localize('notificationsInfoIconForeground', "The color used for the notification info icon.")); /** diff --git a/src/vs/workbench/common/viewlet.ts b/src/vs/workbench/common/viewlet.ts index 3ec5dd0402..fffa1da6f4 100644 --- a/src/vs/workbench/common/viewlet.ts +++ b/src/vs/workbench/common/viewlet.ts @@ -15,5 +15,5 @@ export interface IViewlet extends IComposite { /** * Returns the minimal width needed to avoid any content horizontal truncation */ - getOptimalWidth(): number | null; + getOptimalWidth(): number | undefined; } diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 78c4549a2e..0a743adf6f 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -7,7 +7,6 @@ import { Command } from 'vs/editor/common/modes'; import { UriComponents } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ITreeViewDataProvider } from 'vs/workbench/common/views'; import { localize } from 'vs/nls'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts index 8ca19c5a9c..ac84a41c81 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts @@ -26,13 +26,13 @@ const _ctxCallHierarchyVisible = new RawContextKey('callHierarchyVisibl class CallHierarchyController implements IEditorContribution { - static Id = 'callHierarchy'; + static readonly Id = 'callHierarchy'; static get(editor: ICodeEditor): CallHierarchyController { return editor.getContribution(CallHierarchyController.Id); } - private static _StorageDirection = 'callHierarchy/defaultDirection'; + private static readonly _StorageDirection = 'callHierarchy/defaultDirection'; private readonly _ctxHasProvider: IContextKey; private readonly _ctxIsVisible: IContextKey; @@ -59,10 +59,6 @@ class CallHierarchyController implements IEditorContribution { this._dispoables.dispose(); } - getId(): string { - return CallHierarchyController.Id; - } - async startCallHierarchy(): Promise { this._sessionDisposables.clear(); @@ -115,7 +111,7 @@ class CallHierarchyController implements IEditorContribution { } } -registerEditorContribution(CallHierarchyController); +registerEditorContribution(CallHierarchyController.Id, CallHierarchyController); registerEditorAction(class extends EditorAction { diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts index 08e26868fe..8d109470f6 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts @@ -29,13 +29,13 @@ export interface CallHierarchyItem { } export interface IncomingCall { - source: CallHierarchyItem; - sourceRanges: IRange[]; + from: CallHierarchyItem; + fromRanges: IRange[]; } export interface OutgoingCall { - sourceRanges: IRange[]; - target: CallHierarchyItem; + fromRanges: IRange[]; + to: CallHierarchyItem; } export interface CallHierarchyProvider { diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index 7ed5c18eef..0c0eba65d9 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -295,29 +295,11 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { } localDispose.add(value); - // update: title and subtitle - let node: callHTree.Call | undefined = element; - let names = [element.item.name]; - while (node) { - let parent = this._tree.getParentElement(node); - let name: string; - if (parent instanceof callHTree.Call) { - name = parent.item.name; - node = parent; - } else { - name = this._tree.getInput()!.word; - node = undefined; - } - if (this._direction === CallHierarchyDirection.CallsTo) { - names.push(name); - } else { - names.unshift(name); - } - } + // update: title const title = this._direction === CallHierarchyDirection.CallsFrom ? localize('callFrom', "Calls from '{0}'", this._tree.getInput()!.word) : localize('callsTo', "Callers of '{0}'", this._tree.getInput()!.word); - this.setTitle(title, names.join(' → ')); + this.setTitle(title); })); this._disposables.add(this._editor.onMouseDown(e => { diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts index da33988229..c4cc0deab8 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts @@ -9,7 +9,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; -import { symbolKindToCssClass, Location } from 'vs/editor/common/modes'; +import { SymbolKinds, Location } from 'vs/editor/common/modes'; import { hash } from 'vs/base/common/hash'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { Range } from 'vs/editor/common/core/range'; @@ -83,8 +83,8 @@ export class DataSource implements IAsyncDataSource { const outgoingCalls = await provideOutgoingCalls(model, position, CancellationToken.None); for (const call of outgoingCalls) { bucket.push(new Call( - call.target, - call.sourceRanges.map(range => ({ range, uri: model.uri })), + call.to, + call.fromRanges.map(range => ({ range, uri: model.uri })), parent )); } @@ -94,8 +94,8 @@ export class DataSource implements IAsyncDataSource { const incomingCalls = await provideIncomingCalls(model, position, CancellationToken.None); for (const call of incomingCalls) { bucket.push(new Call( - call.source, - call.sourceRanges.map(range => ({ range, uri: call.source.uri })), + call.from, + call.fromRanges.map(range => ({ range, uri: call.from.uri })), parent )); } @@ -122,7 +122,7 @@ class CallRenderingTemplate { export class CallRenderer implements ITreeRenderer { - static id = 'CallRenderer'; + static readonly id = 'CallRenderer'; templateId: string = CallRenderer.id; @@ -139,7 +139,7 @@ export class CallRenderer implements ITreeRenderer { class InstallAction extends Action { static readonly ID = 'workbench.action.installCommandLine'; - static LABEL = nls.localize('install', "Install '{0}' command in PATH", product.applicationName); + static readonly LABEL = nls.localize('install', "Install '{0}' command in PATH", product.applicationName); constructor( id: string, @@ -122,7 +122,7 @@ class InstallAction extends Action { class UninstallAction extends Action { static readonly ID = 'workbench.action.uninstallCommandLine'; - static LABEL = nls.localize('uninstall', "Uninstall '{0}' command from PATH", product.applicationName); + static readonly LABEL = nls.localize('uninstall', "Uninstall '{0}' command from PATH", product.applicationName); constructor( id: string, diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index 08718cbffe..fabfdf24a0 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -35,7 +35,7 @@ const CONTEXT_ACCESSIBILITY_WIDGET_VISIBLE = new RawContextKey('accessi class AccessibilityHelpController extends Disposable implements IEditorContribution { - private static readonly ID = 'editor.contrib.accessibilityHelpController'; + public static readonly ID = 'editor.contrib.accessibilityHelpController'; public static get(editor: ICodeEditor): AccessibilityHelpController { return editor.getContribution(AccessibilityHelpController.ID); @@ -54,10 +54,6 @@ class AccessibilityHelpController extends Disposable implements IEditorContribut this._widget = this._register(instantiationService.createInstance(AccessibilityHelpWidget, this._editor)); } - public getId(): string { - return AccessibilityHelpController.ID; - } - public show(): void { this._widget.show(); } @@ -299,7 +295,7 @@ class ShowAccessibilityHelpAction extends EditorAction { } } -registerEditorContribution(AccessibilityHelpController); +registerEditorContribution(AccessibilityHelpController.ID, AccessibilityHelpController); registerEditorAction(ShowAccessibilityHelpAction); const AccessibilityHelpCommand = EditorCommand.bindToContribution(AccessibilityHelpController.get); diff --git a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts index c7b0cfcaef..43a1bc3718 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts @@ -5,9 +5,9 @@ import './menuPreventer'; import './accessibility/accessibility'; +import './diffEditorHelper'; import './inspectKeybindings'; import './largeFileOptimizations'; -import './selectionClipboard'; import './inspectTMScopes/inspectTMScopes'; import './toggleMinimap'; import './toggleMultiCursorModifier'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts new file mode 100644 index 0000000000..aa5669c4cd --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -0,0 +1,106 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { registerDiffEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { IDiffEditorContribution } from 'vs/editor/common/editorCommon'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets'; +import { IDiffComputationResult } from 'vs/editor/common/services/editorWorkerService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; + +const enum WidgetState { + Hidden, + HintWhitespace +} + +class DiffEditorHelperContribution extends Disposable implements IDiffEditorContribution { + + public static ID = 'editor.contrib.diffEditorHelper'; + + private _helperWidget: FloatingClickWidget | null; + private _helperWidgetListener: IDisposable | null; + private _state: WidgetState; + + constructor( + private readonly _diffEditor: IDiffEditor, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @INotificationService private readonly _notificationService: INotificationService, + ) { + super(); + this._helperWidget = null; + this._helperWidgetListener = null; + this._state = WidgetState.Hidden; + + + this._register(this._diffEditor.onDidUpdateDiff(() => { + const diffComputationResult = this._diffEditor.getDiffComputationResult(); + this._setState(this._deduceState(diffComputationResult)); + + if (diffComputationResult && diffComputationResult.quitEarly) { + this._notificationService.prompt( + Severity.Warning, + nls.localize('hintTimeout', "The diff algorithm was stopped early (after {0} ms.)", this._diffEditor.maxComputationTime), + [{ + label: nls.localize('removeTimeout', "Remove limit"), + run: () => { + this._configurationService.updateValue('diffEditor.maxComputationTime', 0, ConfigurationTarget.USER); + } + }], + {} + ); + } + })); + } + + private _deduceState(diffComputationResult: IDiffComputationResult | null): WidgetState { + if (!diffComputationResult) { + return WidgetState.Hidden; + } + if (this._diffEditor.ignoreTrimWhitespace && diffComputationResult.changes.length === 0 && !diffComputationResult.identical) { + return WidgetState.HintWhitespace; + } + return WidgetState.Hidden; + } + + private _setState(newState: WidgetState) { + if (this._state === newState) { + return; + } + + this._state = newState; + + if (this._helperWidgetListener) { + this._helperWidgetListener.dispose(); + this._helperWidgetListener = null; + } + if (this._helperWidget) { + this._helperWidget.dispose(); + this._helperWidget = null; + } + + if (this._state === WidgetState.HintWhitespace) { + this._helperWidget = this._instantiationService.createInstance(FloatingClickWidget, this._diffEditor.getModifiedEditor(), nls.localize('hintWhitespace', "Show Whitespace Differences"), null); + this._helperWidgetListener = this._helperWidget.onClick(() => this._onDidClickHelperWidget()); + this._helperWidget.render(); + } + } + + private _onDidClickHelperWidget(): void { + if (this._state === WidgetState.HintWhitespace) { + this._configurationService.updateValue('diffEditor.ignoreTrimWhitespace', false, ConfigurationTarget.USER); + } + } + + dispose(): void { + super.dispose(); + } +} + +registerDiffEditorContribution(DiffEditorHelperContribution.ID, DiffEditorHelperContribution); diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.ts index 27896fa037..8dc4cbc91e 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.ts @@ -27,7 +27,7 @@ import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/work class InspectTMScopesController extends Disposable implements IEditorContribution { - private static readonly ID = 'editor.contrib.inspectTMScopes'; + public static readonly ID = 'editor.contrib.inspectTMScopes'; public static get(editor: ICodeEditor): InspectTMScopesController { return editor.getContribution(InspectTMScopesController.ID); @@ -60,10 +60,6 @@ class InspectTMScopesController extends Disposable implements IEditorContributio this._register(this._editor.onKeyUp((e) => e.keyCode === KeyCode.Escape && this.stop())); } - public getId(): string { - return InspectTMScopesController.ID; - } - public dispose(): void { this.stop(); super.dispose(); @@ -374,7 +370,7 @@ class InspectTMScopesWidget extends Disposable implements IContentWidget { } } -registerEditorContribution(InspectTMScopesController); +registerEditorContribution(InspectTMScopesController.ID, InspectTMScopesController); registerEditorAction(InspectTMScopes); registerThemingParticipant((theme, collector) => { diff --git a/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts b/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts index dbe2ae2d5b..9469bb577b 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts @@ -43,16 +43,7 @@ interface ILanguageConfiguration { } function isStringArr(something: string[] | null): something is string[] { - if (!Array.isArray(something)) { - return false; - } - for (let i = 0, len = something.length; i < len; i++) { - if (typeof something[i] !== 'string') { - return false; - } - } - return true; - + return Array.isArray(something) && something.every(value => typeof value === 'string'); } function isCharacterPair(something: CharacterPair | null): boolean { diff --git a/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts b/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts index c6965c586c..14258d8eef 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts @@ -17,7 +17,7 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ */ export class LargeFileOptimizationsWarner extends Disposable implements IEditorContribution { - private static readonly ID = 'editor.contrib.largeFileOptimizationsWarner'; + public static readonly ID = 'editor.contrib.largeFileOptimizationsWarner'; constructor( private readonly _editor: ICodeEditor, @@ -60,10 +60,6 @@ export class LargeFileOptimizationsWarner extends Disposable implements IEditorC } })); } - - public getId(): string { - return LargeFileOptimizationsWarner.ID; - } } -registerEditorContribution(LargeFileOptimizationsWarner); +registerEditorContribution(LargeFileOptimizationsWarner.ID, LargeFileOptimizationsWarner); diff --git a/src/vs/workbench/contrib/codeEditor/browser/menuPreventer.ts b/src/vs/workbench/contrib/codeEditor/browser/menuPreventer.ts index 78bdaf6b88..facd9e8298 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/menuPreventer.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/menuPreventer.ts @@ -14,7 +14,7 @@ import { IEditorContribution } from 'vs/editor/common/editorCommon'; */ export class MenuPreventer extends Disposable implements IEditorContribution { - private static readonly ID = 'editor.contrib.menuPreventer'; + public static readonly ID = 'editor.contrib.menuPreventer'; private _editor: ICodeEditor; private _altListeningMouse: boolean; @@ -55,10 +55,6 @@ export class MenuPreventer extends Disposable implements IEditorContribution { } })); } - - public getId(): string { - return MenuPreventer.ID; - } } -registerEditorContribution(MenuPreventer); +registerEditorContribution(MenuPreventer.ID, MenuPreventer); diff --git a/src/vs/workbench/contrib/codeEditor/browser/selectionClipboard.ts b/src/vs/workbench/contrib/codeEditor/browser/selectionClipboard.ts index 7c46216773..515bc7ad1d 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/selectionClipboard.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/selectionClipboard.ts @@ -3,114 +3,4 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { RunOnceScheduler } from 'vs/base/common/async'; -import { Disposable } from 'vs/base/common/lifecycle'; -import * as process from 'vs/base/common/process'; -import * as platform from 'vs/base/common/platform'; -import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; -import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; -import { ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; -import { Range } from 'vs/editor/common/core/range'; -import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { EndOfLinePreference } from 'vs/editor/common/model'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; - -export class SelectionClipboard extends Disposable implements IEditorContribution { - private static SELECTION_LENGTH_LIMIT = 65536; - private static readonly ID = 'editor.contrib.selectionClipboard'; - - constructor(editor: ICodeEditor, @IClipboardService clipboardService: IClipboardService) { - super(); - - if (platform.isLinux) { - let isEnabled = editor.getOption(EditorOption.selectionClipboard); - - this._register(editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { - if (e.hasChanged(EditorOption.selectionClipboard)) { - isEnabled = editor.getOption(EditorOption.selectionClipboard); - } - })); - - this._register(editor.onMouseDown((e: IEditorMouseEvent) => { - if (!isEnabled) { - return; - } - if (!editor.hasModel()) { - return; - } - if (e.event.middleButton) { - e.event.preventDefault(); - editor.focus(); - - if (e.target.position) { - editor.setPosition(e.target.position); - } - - if (e.target.type === MouseTargetType.SCROLLBAR) { - return; - } - - process.nextTick(() => { - // TODO@Alex: electron weirdness: calling clipboard.readText('selection') generates a paste event, so no need to execute paste ourselves - clipboardService.readText('selection'); - // keybindingService.executeCommand(Handler.Paste, { - // text: clipboard.readText('selection'), - // pasteOnNewLine: false - // }); - }); - } - })); - - let setSelectionToClipboard = this._register(new RunOnceScheduler(() => { - if (!editor.hasModel()) { - return; - } - let model = editor.getModel(); - let selections = editor.getSelections(); - selections = selections.slice(0); - selections.sort(Range.compareRangesUsingStarts); - - let resultLength = 0; - for (const sel of selections) { - if (sel.isEmpty()) { - // Only write if all cursors have selection - return; - } - resultLength += model.getValueLengthInRange(sel); - } - - if (resultLength > SelectionClipboard.SELECTION_LENGTH_LIMIT) { - // This is a large selection! - // => do not write it to the selection clipboard - return; - } - - let result: string[] = []; - for (const sel of selections) { - result.push(model.getValueInRange(sel, EndOfLinePreference.TextDefined)); - } - - let textToCopy = result.join(model.getEOL()); - clipboardService.writeText(textToCopy, 'selection'); - }, 100)); - - this._register(editor.onDidChangeCursorSelection((e: ICursorSelectionChangedEvent) => { - if (!isEnabled) { - return; - } - setSelectionToClipboard.schedule(); - })); - } - } - - public getId(): string { - return SelectionClipboard.ID; - } - - public dispose(): void { - super.dispose(); - } -} - -registerEditorContribution(SelectionClipboard); +export const SelectionClipboardContributionID = 'editor.contrib.selectionClipboard'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts b/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts index a6f90bd70a..7049dfadbd 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts @@ -9,8 +9,9 @@ import { ContextMenuController } from 'vs/editor/contrib/contextmenu/contextmenu import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer'; -import { SelectionClipboard } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; +import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; import { TabCompletionController } from 'vs/workbench/contrib/snippets/browser/tabCompletion'; +import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; export function getSimpleEditorOptions(): IEditorOptions { return { @@ -40,13 +41,13 @@ export function getSimpleEditorOptions(): IEditorOptions { export function getSimpleCodeEditorWidgetOptions(): ICodeEditorWidgetOptions { return { isSimpleWidget: true, - contributions: [ - MenuPreventer, - SelectionClipboard, - ContextMenuController, - SuggestController, - SnippetController2, - TabCompletionController, - ] + contributions: EditorExtensionsRegistry.getSomeEditorContributions([ + MenuPreventer.ID, + SelectionClipboardContributionID, + ContextMenuController.ID, + SuggestController.ID, + SnippetController2.ID, + TabCompletionController.ID, + ]) }; } diff --git a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.css b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.css index 95628ada2a..b0680b67ae 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.css +++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.css @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ .suggest-input-container { - padding: 3px 4px 5px; + padding: 4px; } .suggest-input-container .monaco-editor-background, diff --git a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts index f4349db67c..f90f89d6ae 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts @@ -31,7 +31,8 @@ import { IStyleOverrides, IThemable, attachStyler } from 'vs/platform/theme/comm import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; -import { SelectionClipboard } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; +import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; +import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; interface SuggestResultsProvider { /** @@ -130,7 +131,13 @@ export class SuggestEnabledInput extends Widget implements IThemable { this.inputWidget = instantiationService.createInstance(CodeEditorWidget, this.stylingContainer, editorOptions, { - contributions: [SuggestController, SnippetController2, ContextMenuController, MenuPreventer, SelectionClipboard], + contributions: EditorExtensionsRegistry.getSomeEditorContributions([ + SuggestController.ID, + SnippetController2.ID, + ContextMenuController.ID, + MenuPreventer.ID, + SelectionClipboardContributionID, + ]), isSimpleWidget: true, }); this._register(this.inputWidget); @@ -216,7 +223,7 @@ export class SuggestEnabledInput extends Widget implements IThemable { public style(colors: ISuggestEnabledInputStyles): void { - this.stylingContainer.style.backgroundColor = colors.inputBackground ? colors.inputBackground.toString() : null; + this.stylingContainer.style.backgroundColor = colors.inputBackground ? colors.inputBackground.toString() : ''; this.stylingContainer.style.color = colors.inputForeground ? colors.inputForeground.toString() : null; this.placeholderText.style.color = colors.inputPlaceholderForeground ? colors.inputPlaceholderForeground.toString() : null; @@ -228,7 +235,7 @@ export class SuggestEnabledInput extends Widget implements IThemable { const cursor = this.stylingContainer.getElementsByClassName('cursor')[0] as HTMLDivElement; if (cursor) { - cursor.style.backgroundColor = colors.inputForeground ? colors.inputForeground.toString() : null; + cursor.style.backgroundColor = colors.inputForeground ? colors.inputForeground.toString() : ''; } } @@ -290,6 +297,7 @@ function getSuggestEnabledInputOptions(ariaLabel?: string): IEditorOptions { ariaLabel: ariaLabel || '', snippetSuggestions: 'none', - suggest: { filterGraceful: false, showIcons: false } + suggest: { filterGraceful: false, showIcons: false }, + autoClosingBrackets: 'never' }; } diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index ac0779e5c3..04652e39e0 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts @@ -166,7 +166,7 @@ class ToggleWordWrapAction extends EditorAction { class ToggleWordWrapController extends Disposable implements IEditorContribution { - private static readonly _ID = 'editor.contrib.toggleWordWrapController'; + public static readonly ID = 'editor.contrib.toggleWordWrapController'; constructor( private readonly editor: ICodeEditor, @@ -254,10 +254,6 @@ class ToggleWordWrapController extends Disposable implements IEditorContribution wordWrapMinified: state.configuredWordWrapMinified }); } - - public getId(): string { - return ToggleWordWrapController._ID; - } } function canToggleWordWrap(uri: URI): boolean { @@ -268,7 +264,7 @@ function canToggleWordWrap(uri: URI): boolean { } -registerEditorContribution(ToggleWordWrapController); +registerEditorContribution(ToggleWordWrapController.ID, ToggleWordWrapController); registerEditorAction(ToggleWordWrapAction); diff --git a/src/vs/workbench/contrib/codeEditor/browser/workbenchReferenceSearch.ts b/src/vs/workbench/contrib/codeEditor/browser/workbenchReferenceSearch.ts index cee0da7f84..ea8f40e964 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/workbenchReferenceSearch.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/workbenchReferenceSearch.ts @@ -37,4 +37,4 @@ export class WorkbenchReferencesController extends ReferencesController { } } -registerEditorContribution(WorkbenchReferencesController); +registerEditorContribution(ReferencesController.ID, WorkbenchReferencesController); diff --git a/src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts index 5af54b676a..6000fabe0e 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts @@ -4,3 +4,4 @@ *--------------------------------------------------------------------------------------------*/ import './sleepResumeRepaintMinimap'; +import './selectionClipboard'; diff --git a/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts new file mode 100644 index 0000000000..51e88400c7 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts @@ -0,0 +1,95 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { RunOnceScheduler } from 'vs/base/common/async'; +import { Disposable } from 'vs/base/common/lifecycle'; +import * as platform from 'vs/base/common/platform'; +import { ICodeEditor, IEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; +import { ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; +import { Range } from 'vs/editor/common/core/range'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { EndOfLinePreference } from 'vs/editor/common/model'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; + +export class SelectionClipboard extends Disposable implements IEditorContribution { + private static readonly SELECTION_LENGTH_LIMIT = 65536; + + constructor(editor: ICodeEditor, @IClipboardService clipboardService: IClipboardService) { + super(); + + if (platform.isLinux) { + let isEnabled = editor.getOption(EditorOption.selectionClipboard); + + this._register(editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { + if (e.hasChanged(EditorOption.selectionClipboard)) { + isEnabled = editor.getOption(EditorOption.selectionClipboard); + } + })); + + this._register(editor.onMouseUp((e: IEditorMouseEvent) => { + if (!isEnabled) { + if (e.event.middleButton) { + // try to stop the upcoming paste + e.event.preventDefault(); + } + } + })); + + let setSelectionToClipboard = this._register(new RunOnceScheduler(() => { + if (!editor.hasModel()) { + return; + } + let model = editor.getModel(); + let selections = editor.getSelections(); + selections = selections.slice(0); + selections.sort(Range.compareRangesUsingStarts); + + let resultLength = 0; + for (const sel of selections) { + if (sel.isEmpty()) { + // Only write if all cursors have selection + return; + } + resultLength += model.getValueLengthInRange(sel); + } + + if (resultLength > SelectionClipboard.SELECTION_LENGTH_LIMIT) { + // This is a large selection! + // => do not write it to the selection clipboard + return; + } + + let result: string[] = []; + for (const sel of selections) { + result.push(model.getValueInRange(sel, EndOfLinePreference.TextDefined)); + } + + let textToCopy = result.join(model.getEOL()); + clipboardService.writeText(textToCopy, 'selection'); + }, 100)); + + this._register(editor.onDidChangeCursorSelection((e: ICursorSelectionChangedEvent) => { + if (!isEnabled) { + return; + } + if (e.source === 'restoreState') { + // do not set selection to clipboard if this selection change + // was caused by restoring editors... + return; + } + setSelectionToClipboard.schedule(); + })); + } + } + + public dispose(): void { + super.dispose(); + } +} + +registerEditorContribution(SelectionClipboardContributionID, SelectionClipboard); diff --git a/src/vs/workbench/contrib/comments/browser/commentMenus.ts b/src/vs/workbench/contrib/comments/browser/commentMenus.ts index 85e0080412..4c15115a1f 100644 --- a/src/vs/workbench/contrib/comments/browser/commentMenus.ts +++ b/src/vs/workbench/contrib/comments/browser/commentMenus.ts @@ -7,22 +7,15 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; import { IAction } from 'vs/base/common/actions'; -import { MainThreadCommentController } from 'vs/workbench/api/browser/mainThreadComments'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Comment, CommentThread } from 'vs/editor/common/modes'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; export class CommentMenus implements IDisposable { constructor( - controller: MainThreadCommentController, - @IContextKeyService private contextKeyService: IContextKeyService, @IMenuService private readonly menuService: IMenuService, @IContextMenuService private readonly contextMenuService: IContextMenuService - ) { - const commentControllerKey = this.contextKeyService.createKey('commentController', undefined); - - commentControllerKey.set(controller.contextValue); - } + ) { } getCommentThreadTitleActions(commentThread: CommentThread, contextKeyService: IContextKeyService): IMenu { return this.getMenu(MenuId.CommentThreadTitle, contextKeyService); diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index d660861098..6fda8f97d0 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -160,9 +160,7 @@ export class CommentService extends Disposable implements ICommentService { return this._commentMenus.get(owner)!; } - let controller = this._commentControls.get(owner); - - let menu = this.instantiationService.createInstance(CommentMenus, controller!); + let menu = this.instantiationService.createInstance(CommentMenus); this._commentMenus.set(owner, menu); return menu; } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index b53e072236..c7cb23d753 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -45,6 +45,7 @@ import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/comment import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; import { SimpleCommentEditor } from './simpleCommentEditor'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration'; const COLLAPSE_ACTION_CLASS = 'expand-review-action'; @@ -54,21 +55,21 @@ const COMMENT_SCHEME = 'comment'; let INMEM_MODEL_ID = 0; export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget { - private _headElement: HTMLElement; - protected _headingLabel: HTMLElement; - protected _actionbarWidget: ActionBar; - private _bodyElement: HTMLElement; + private _headElement!: HTMLElement; + protected _headingLabel!: HTMLElement; + protected _actionbarWidget!: ActionBar; + private _bodyElement!: HTMLElement; private _parentEditor: ICodeEditor; - private _commentEditor: ICodeEditor; - private _commentsElement: HTMLElement; - private _commentElements: CommentNode[]; - private _commentForm: HTMLElement; - private _reviewThreadReplyButton: HTMLElement; + private _commentEditor!: ICodeEditor; + private _commentsElement!: HTMLElement; + private _commentElements: CommentNode[] = []; + private _commentForm!: HTMLElement; + private _reviewThreadReplyButton!: HTMLElement; private _resizeObserver: any; private readonly _onDidClose = new Emitter(); private readonly _onDidCreateThread = new Emitter(); private _isExpanded?: boolean; - private _collapseAction: Action; + private _collapseAction!: Action; private _commentGlyph?: CommentGlyphWidget; private _submitActionsDisposables: IDisposable[]; private readonly _globalToDispose = new DisposableStore(); @@ -76,12 +77,13 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget private _markdownRenderer: MarkdownRenderer; private _styleElement: HTMLStyleElement; private _formActions: HTMLElement | null; - private _error: HTMLElement; + private _error!: HTMLElement; private _contextKeyService: IContextKeyService; private _threadIsEmpty: IContextKey; private _commentThreadContextValue: IContextKey; - private _commentEditorIsEmpty: IContextKey; - private _commentFormActions: CommentFormActions; + private _commentEditorIsEmpty!: IContextKey; + private _commentFormActions!: CommentFormActions; + private _scopedInstatiationService: IInstantiationService; public get owner(): string { return this._owner; @@ -100,8 +102,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget editor: ICodeEditor, private _owner: string, private _commentThread: modes.CommentThread, - private _pendingComment: string, - @IInstantiationService private instantiationService: IInstantiationService, + private _pendingComment: string | null, + @IInstantiationService instantiationService: IInstantiationService, @IModeService private modeService: IModeService, @IModelService private modelService: IModelService, @IThemeService private themeService: IThemeService, @@ -114,10 +116,22 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget ) { super(editor, { keepEditorSelection: true }); this._contextKeyService = contextKeyService.createScoped(this.domNode); + + this._scopedInstatiationService = instantiationService.createChild(new ServiceCollection( + [IContextKeyService, this._contextKeyService] + )); + this._threadIsEmpty = CommentContextKeys.commentThreadIsEmpty.bindTo(this._contextKeyService); this._threadIsEmpty.set(!_commentThread.comments || !_commentThread.comments.length); this._commentThreadContextValue = this._contextKeyService.createKey('commentThread', _commentThread.contextValue); + const commentControllerKey = this._contextKeyService.createKey('commentController', undefined); + const controller = this.commentService.getCommentController(this._owner); + + if (controller) { + commentControllerKey.set(controller.contextValue); + } + this._resizeObserver = null; this._isExpanded = _commentThread.collapsibleState === modes.CommentThreadCollapsibleState.Expanded; this._commentThreadDisposables = []; @@ -332,12 +346,6 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentElements = newCommentNodeList; this.createThreadLabel(); - if (this._formActions && this._commentEditor.hasModel()) { - dom.clearNode(this._formActions); - const model = this._commentEditor.getModel(); - this.createCommentWidgetActions(this._formActions, model); - } - // Move comment glyph widget and show position if the line has changed. const lineNumber = this._commentThread.range.startLineNumber; let shouldMoveWidget = false; @@ -406,7 +414,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget const hasExistingComments = this._commentThread.comments && this._commentThread.comments.length > 0; this._commentForm = dom.append(this._bodyElement, dom.$('.comment-form')); - this._commentEditor = this.instantiationService.createInstance(SimpleCommentEditor, this._commentForm, SimpleCommentEditor.getEditorOptions(), this._parentEditor, this); + this._commentEditor = this._scopedInstatiationService.createInstance(SimpleCommentEditor, this._commentForm, SimpleCommentEditor.getEditorOptions(), this._parentEditor, this); this._commentEditorIsEmpty = CommentContextKeys.commentIsEmpty.bindTo(this._contextKeyService); this._commentEditorIsEmpty.set(!this._pendingComment); @@ -595,7 +603,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } private createNewCommentNode(comment: modes.Comment): CommentNode { - let newCommentNode = this.instantiationService.createInstance(CommentNode, + let newCommentNode = this._scopedInstatiationService.createInstance(CommentNode, this._commentThread, comment, this.owner, @@ -739,7 +747,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } } - private mouseDownInfo: { lineNumber: number } | null; + private mouseDownInfo: { lineNumber: number } | null = null; private onEditorMouseDown(e: IEditorMouseEvent): void { this.mouseDownInfo = null; diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index 814f68a88f..164f062e28 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -76,10 +76,7 @@ class CommentingRangeDecoration { options: commentingOptions }]; - let model = this._editor.getModel(); - if (model) { - this._decorationId = model.deltaDecorations([this._decorationId], commentingRangeDecorations)[0]; - } + this._decorationId = this._editor.deltaDecorations([], commentingRangeDecorations)[0]; } public getCommentAction(): { ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges } { @@ -149,20 +146,20 @@ class CommentingRangeDecorator { } } -export class ReviewController implements IEditorContribution { +export class CommentController implements IEditorContribution { private readonly globalToDispose = new DisposableStore(); private readonly localToDispose = new DisposableStore(); - private editor: ICodeEditor; + private editor!: ICodeEditor; private _commentWidgets: ReviewZoneWidget[]; private _commentInfos: ICommentInfo[]; - private _commentingRangeDecorator: CommentingRangeDecorator; + private _commentingRangeDecorator!: CommentingRangeDecorator; private mouseDownInfo: { lineNumber: number } | null = null; private _commentingRangeSpaceReserved = false; private _computePromise: CancelablePromise> | null; - private _addInProgress: boolean; + private _addInProgress!: boolean; private _emptyThreadsToAddQueue: [number, IEditorMouseEvent | undefined][] = []; - private _computeCommentingRangePromise: CancelablePromise | null; - private _computeCommentingRangeScheduler: Delayer> | null; + private _computeCommentingRangePromise!: CancelablePromise | null; + private _computeCommentingRangeScheduler!: Delayer> | null; private _pendingCommentCache: { [key: string]: { [key: string]: string } }; constructor( @@ -246,8 +243,8 @@ export class ReviewController implements IEditorContribution { } } - public static get(editor: ICodeEditor): ReviewController { - return editor.getContribution(ID); + public static get(editor: ICodeEditor): CommentController { + return editor.getContribution(ID); } public revealCommentThread(threadId: string, commentUniqueId: number, fetchOnceIfNotExist: boolean): void { @@ -317,10 +314,6 @@ export class ReviewController implements IEditorContribution { } } - public getId(): string { - return ID; - } - public dispose(): void { this.globalToDispose.dispose(); this.localToDispose.dispose(); @@ -689,7 +682,7 @@ export class NextCommentThreadAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - let controller = ReviewController.get(editor); + let controller = CommentController.get(editor); if (controller) { controller.nextCommentThread(); } @@ -697,7 +690,7 @@ export class NextCommentThreadAction extends EditorAction { } -registerEditorContribution(ReviewController); +registerEditorContribution(ID, CommentController); registerEditorAction(NextCommentThreadAction); CommandsRegistry.registerCommand({ @@ -708,7 +701,7 @@ CommandsRegistry.registerCommand({ return Promise.resolve(); } - const controller = ReviewController.get(activeEditor); + const controller = CommentController.get(activeEditor); if (!controller) { return Promise.resolve(); } diff --git a/src/vs/workbench/contrib/comments/browser/commentsPanel.ts b/src/vs/workbench/contrib/comments/browser/commentsPanel.ts index f23396f2a3..ccaf7feef7 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsPanel.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsPanel.ts @@ -15,7 +15,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Panel } from 'vs/workbench/browser/panel'; import { CommentNode, CommentsModel, ResourceWithCommentThreads, ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel'; -import { ReviewController } from 'vs/workbench/contrib/comments/browser/commentsEditorContribution'; +import { CommentController } from 'vs/workbench/contrib/comments/browser/commentsEditorContribution'; import { ICommentService, IWorkspaceCommentThreadsEvent } from 'vs/workbench/contrib/comments/browser/commentService'; import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -27,13 +27,13 @@ import { CommentsList, COMMENTS_PANEL_ID, COMMENTS_PANEL_TITLE } from 'vs/workbe export class CommentsPanel extends Panel { - private treeLabels: ResourceLabels; - private tree: CommentsList; - private treeContainer: HTMLElement; - private messageBoxContainer: HTMLElement; - private messageBox: HTMLElement; - private commentsModel: CommentsModel; - private collapseAllAction: IAction; + private treeLabels!: ResourceLabels; + private tree!: CommentsList; + private treeContainer!: HTMLElement; + private messageBoxContainer!: HTMLElement; + private messageBox!: HTMLElement; + private commentsModel!: CommentsModel; + private collapseAllAction?: IAction; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -163,7 +163,7 @@ export class CommentsPanel extends Panel { const commentToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].comment.uniqueIdInThread : element.comment.uniqueIdInThread; const control = this.editorService.activeTextEditorWidget; if (threadToReveal && isCodeEditor(control)) { - const controller = ReviewController.get(control); + const controller = CommentController.get(control); controller.revealCommentThread(threadToReveal, commentToReveal, false); } @@ -184,7 +184,7 @@ export class CommentsPanel extends Panel { if (editor) { const control = editor.getControl(); if (threadToReveal && isCodeEditor(control)) { - const controller = ReviewController.get(control); + const controller = CommentController.get(control); controller.revealCommentThread(threadToReveal, commentToReveal.uniqueIdInThread, true); } } @@ -195,7 +195,9 @@ export class CommentsPanel extends Panel { private refresh(): void { if (this.isVisible()) { - this.collapseAllAction.enabled = this.commentsModel.hasCommentThreads(); + if (this.collapseAllAction) { + this.collapseAllAction.enabled = this.commentsModel.hasCommentThreads(); + } dom.toggleClass(this.treeContainer, 'hidden', !this.commentsModel.hasCommentThreads()); this.tree.updateChildren().then(() => { diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 48c68e15ee..e718cb1b9f 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -56,8 +56,8 @@ interface ICommentThreadTemplateData { } export class CommentsModelVirualDelegate implements IListVirtualDelegate { - private static RESOURCE_ID = 'resource-with-comments'; - private static COMMENT_ID = 'comment-node'; + private static readonly RESOURCE_ID = 'resource-with-comments'; + private static readonly COMMENT_ID = 'comment-node'; getHeight(element: any): number { diff --git a/src/vs/workbench/contrib/comments/browser/media/review.css b/src/vs/workbench/contrib/comments/browser/media/review.css index bd0188436a..41c1ad8039 100644 --- a/src/vs/workbench/contrib/comments/browser/media/review.css +++ b/src/vs/workbench/contrib/comments/browser/media/review.css @@ -34,7 +34,7 @@ width: 100%; } -.monaco-editor .review-widget .body .review-comment .comment-title .action-label.octicon { +.monaco-editor .review-widget .body .review-comment .comment-title .action-label.codicon { line-height: 18px; } @@ -413,7 +413,7 @@ background-position: center center; } -.monaco-editor .review-widget .head .review-actions > .monaco-action-bar .action-label.octicon { +.monaco-editor .review-widget .head .review-actions > .monaco-action-bar .action-label.codicon { margin: 0; } diff --git a/src/vs/workbench/contrib/comments/browser/reactionsAction.ts b/src/vs/workbench/contrib/comments/browser/reactionsAction.ts index 9660601b9e..94962c7bb2 100644 --- a/src/vs/workbench/contrib/comments/browser/reactionsAction.ts +++ b/src/vs/workbench/contrib/comments/browser/reactionsAction.ts @@ -33,6 +33,10 @@ export class ReactionActionViewItem extends ActionViewItem { super(null, action, {}); } updateLabel(): void { + if (!this.label) { + return; + } + let action = this.getAction() as ReactionAction; if (action.class) { this.label.classList.add(action.class); diff --git a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts index 431c38e425..d621320c75 100644 --- a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts +++ b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts @@ -48,11 +48,11 @@ export class SimpleCommentEditor extends CodeEditorWidget { ) { const codeEditorWidgetOptions = { contributions: [ - MenuPreventer, - ContextMenuController, - SuggestController, - SnippetController2, - TabCompletionController, + { id: MenuPreventer.ID, ctor: MenuPreventer }, + { id: ContextMenuController.ID, ctor: ContextMenuController }, + { id: SuggestController.ID, ctor: SuggestController }, + { id: SnippetController2.ID, ctor: SnippetController2 }, + { id: TabCompletionController.ID, ctor: TabCompletionController }, ] }; diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index 41c7778dcf..a6c40d7a3b 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// import { Schemas } from 'vs/base/common/network'; import { firstOrDefault } from 'vs/base/common/arrays'; import { URI } from 'vs/base/common/uri'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; @@ -13,8 +12,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IListService } from 'vs/platform/list/browser/listService'; import { IEditorCommandsContext } from 'vs/workbench/common/editor'; -// import { ResourceContextKey } from 'vs/workbench/common/resources'; -import { ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { ICustomEditorService, CONTEXT_HAS_CUSTOM_EDITORS } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -95,7 +93,8 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { id: REOPEN_WITH_COMMAND_ID, title: REOPEN_WITH_TITLE, category: viewCategory, - } + }, + when: CONTEXT_HAS_CUSTOM_EDITORS, }); // #endregion diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 8a0c68a97d..0caa17305b 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -5,26 +5,24 @@ import { memoize } from 'vs/base/common/decorators'; import { Emitter } from 'vs/base/common/event'; +import { Lazy } from 'vs/base/common/lazy'; import { UnownedDisposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; import { basename } from 'vs/base/common/path'; +import { DataUri, isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { WebviewContentState } from 'vs/editor/common/modes'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IEditorModel } from 'vs/platform/editor/common/editor'; import { ILabelService } from 'vs/platform/label/common/label'; import { ConfirmResult, IEditorInput, Verbosity } from 'vs/workbench/common/editor'; import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; -import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; -import { IWebviewEditorService } from 'vs/workbench/contrib/webview/browser/webviewEditorService'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IWebviewWorkbenchService, LazilyResolvedWebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; import { promptSave } from 'vs/workbench/services/textfile/browser/textFileService'; -export class CustomFileEditorInput extends WebviewInput { +export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { public static typeId = 'workbench.editors.webviewEditor'; - private name?: string; - private _hasResolved = false; private readonly _editorResource: URI; private _state = WebviewContentState.Readonly; @@ -32,13 +30,12 @@ export class CustomFileEditorInput extends WebviewInput { resource: URI, viewType: string, id: string, - webview: UnownedDisposable, - @ILabelService private readonly labelService: ILabelService, - @IWebviewEditorService private readonly _webviewEditorService: IWebviewEditorService, - @IExtensionService private readonly _extensionService: IExtensionService, + webview: Lazy>, + @IWebviewWorkbenchService webviewWorkbenchService: IWebviewWorkbenchService, @IDialogService private readonly dialogService: IDialogService, + @ILabelService private readonly labelService: ILabelService, ) { - super(id, viewType, '', webview); + super(id, viewType, '', webview, webviewWorkbenchService); this._editorResource = resource; } @@ -50,17 +47,34 @@ export class CustomFileEditorInput extends WebviewInput { return this._editorResource; } + @memoize getName(): string { - if (!this.name) { - this.name = basename(this.labelService.getUriLabel(this.getResource())); + if (this.getResource().scheme === Schemas.data) { + const metadata = DataUri.parseMetaData(this.getResource()); + const label = metadata.get(DataUri.META_DATA_LABEL); + if (typeof label === 'string') { + return label; + } } - return this.name; + return basename(this.labelService.getUriLabel(this.getResource())); + } + + @memoize + getDescription(): string | undefined { + if (this.getResource().scheme === Schemas.data) { + const metadata = DataUri.parseMetaData(this.getResource()); + const description = metadata.get(DataUri.META_DATA_DESCRIPTION); + if (typeof description === 'string') { + return description; + } + } + return super.getDescription(); } matches(other: IEditorInput): boolean { return this === other || (other instanceof CustomFileEditorInput && this.viewType === other.viewType - && this.getResource().toString() === other.getResource().toString()); + && isEqual(this.getResource(), other.getResource())); } @memoize @@ -70,11 +84,17 @@ export class CustomFileEditorInput extends WebviewInput { @memoize private get mediumTitle(): string { + if (this.getResource().scheme === Schemas.data) { + return this.getName(); + } return this.labelService.getUriLabel(this.getResource(), { relative: true }); } @memoize private get longTitle(): string { + if (this.getResource().scheme === Schemas.data) { + return this.getName(); + } return this.labelService.getUriLabel(this.getResource()); } @@ -90,15 +110,6 @@ export class CustomFileEditorInput extends WebviewInput { } } - public async resolve(): Promise { - if (!this._hasResolved) { - this._hasResolved = true; - this._extensionService.activateByEvent(`onWebviewEditor:${this.viewType}`); - await this._webviewEditorService.resolveWebview(this); - } - return super.resolve(); - } - public setState(newState: WebviewContentState): void { this._state = newState; this._onDidChangeDirty.fire(); diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts index 069bebd332..9e8189fc3d 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -9,7 +9,8 @@ import { generateUuid } from 'vs/base/common/uuid'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { CustomFileEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; import { WebviewEditorInputFactory } from 'vs/workbench/contrib/webview/browser/webviewEditorInputFactory'; -import { IWebviewEditorService } from 'vs/workbench/contrib/webview/browser/webviewEditorService'; +import { IWebviewWorkbenchService } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; +import { Lazy } from 'vs/base/common/lazy'; export class CustomEditoInputFactory extends WebviewEditorInputFactory { @@ -17,9 +18,9 @@ export class CustomEditoInputFactory extends WebviewEditorInputFactory { public constructor( @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IWebviewEditorService private readonly webviewService: IWebviewEditorService, + @IWebviewWorkbenchService private readonly webviewWorkbenchService: IWebviewWorkbenchService, ) { - super(webviewService); + super(webviewWorkbenchService); } public serialize(input: CustomFileEditorInput): string | undefined { @@ -41,12 +42,16 @@ export class CustomEditoInputFactory extends WebviewEditorInputFactory { ): CustomFileEditorInput { const data = this.fromJson(serializedEditorInput); const id = data.id || generateUuid(); - const webviewInput = this.webviewService.reviveWebview(id, data.viewType, data.title, data.iconPath, data.state, data.options, data.extensionLocation ? { - location: data.extensionLocation, - id: data.extensionId - } : undefined, data.group); - const customInput = this._instantiationService.createInstance(CustomFileEditorInput, URI.from((data as any).editorResource), data.viewType, id, new UnownedDisposable(webviewInput.webview)); + const webview = new Lazy(() => { + const webviewInput = this.webviewWorkbenchService.reviveWebview(id, data.viewType, data.title, data.iconPath, data.state, data.options, data.extensionLocation && data.extensionId ? { + location: data.extensionLocation, + id: data.extensionId + } : undefined, data.group); + return new UnownedDisposable(webviewInput.webview); + }); + + const customInput = this._instantiationService.createInstance(CustomFileEditorInput, URI.from((data as any).editorResource), data.viewType, id, webview); if (typeof data.group === 'number') { customInput.updateGroup(data.group); } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 885102e57f..7f80c34a25 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -3,12 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { coalesce, distinct } from 'vs/base/common/arrays'; +import { coalesce, distinct, mergeSort, find } from 'vs/base/common/arrays'; import * as glob from 'vs/base/common/glob'; -import { UnownedDisposable } from 'vs/base/common/lifecycle'; +import { UnownedDisposable, Disposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { basename, DataUri, isEqual } from 'vs/base/common/resources'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import * as nls from 'vs/nls'; @@ -22,12 +21,14 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { EditorInput, EditorOptions, IEditor, IEditorInput } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { webviewEditorsExtensionPoint } from 'vs/workbench/contrib/customEditor/browser/extensionPoint'; -import { CustomEditorDiscretion, CustomEditorInfo, CustomEditorSelector, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { CustomEditorPriority, CustomEditorInfo, CustomEditorSelector, ICustomEditorService, CONTEXT_HAS_CUSTOM_EDITORS } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; import { CustomFileEditorInput } from './customEditorInput'; +import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { Lazy } from 'vs/base/common/lazy'; const defaultEditorId = 'default'; @@ -37,7 +38,7 @@ const defaultEditorInfo: CustomEditorInfo = { selector: [ { filenamePattern: '*' } ], - discretion: CustomEditorDiscretion.default, + priority: CustomEditorPriority.default, }; export class CustomEditorStore { @@ -67,18 +68,22 @@ export class CustomEditorStore { } } -export class CustomEditorService implements ICustomEditorService { +export class CustomEditorService extends Disposable implements ICustomEditorService { _serviceBrand: any; private readonly editors = new CustomEditorStore(); + private readonly _hasCustomEditor: IContextKey; constructor( + @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IQuickInputService private readonly quickInputService: IQuickInputService, @IWebviewService private readonly webviewService: IWebviewService, ) { + super(); + webviewEditorsExtensionPoint.setHandler(extensions => { this.editors.clear(); @@ -88,11 +93,17 @@ export class CustomEditorService implements ICustomEditorService { id: webviewEditorContribution.viewType, displayName: webviewEditorContribution.displayName, selector: webviewEditorContribution.selector || [], - discretion: webviewEditorContribution.discretion || CustomEditorDiscretion.default, + priority: webviewEditorContribution.priority || CustomEditorPriority.default, }); } } + this.updateContext(); }); + + this._hasCustomEditor = CONTEXT_HAS_CUSTOM_EDITORS.bindTo(contextKeyService); + + this._register(this.editorService.onDidActiveEditorChange(() => this.updateContext())); + this.updateContext(); } public getContributedCustomEditors(resource: URI): readonly CustomEditorInfo[] { @@ -117,11 +128,22 @@ export class CustomEditorService implements ICustomEditorService { ...this.getContributedCustomEditors(resource), ], editor => editor.id); - const pick = await this.quickInputService.pick( - customEditors.map((editorDescriptor): IQuickPickItem => ({ - label: editorDescriptor.displayName, - id: editorDescriptor.id, - })), { + let currentlyOpenedEditorType: undefined | string; + for (const editor of group ? group.editors : []) { + if (editor.getResource() && isEqual(editor.getResource()!, resource)) { + currentlyOpenedEditorType = editor instanceof CustomFileEditorInput ? editor.viewType : defaultEditorId; + break; + } + } + + const items = customEditors.map((editorDescriptor): IQuickPickItem => ({ + label: editorDescriptor.displayName, + id: editorDescriptor.id, + description: editorDescriptor.id === currentlyOpenedEditorType + ? nls.localize('openWithCurrentlyActive', "Currently Active") + : undefined + })); + const pick = await this.quickInputService.pick(items, { placeHolder: nls.localize('promptOpenWith.placeHolder', "Select editor to use for '{0}'...", basename(resource)), }); @@ -154,13 +176,15 @@ export class CustomEditorService implements ICustomEditorService { resource: URI, viewType: string, group: IEditorGroup | undefined, - options?: { readonly customClasses: string }, + options?: { readonly customClasses: string; }, ): CustomFileEditorInput { const id = generateUuid(); - const webview = this.webviewService.createWebviewEditorOverlay(id, { customClasses: options ? options.customClasses : undefined }, {}); - const input = this.instantiationService.createInstance(CustomFileEditorInput, resource, viewType, id, new UnownedDisposable(webview)); + const webview = new Lazy(() => { + return new UnownedDisposable(this.webviewService.createWebviewEditorOverlay(id, { customClasses: options ? options.customClasses : undefined }, {})); + }); + const input = this.instantiationService.createInstance(CustomFileEditorInput, resource, viewType, id, webview); if (group) { - input.updateGroup(group!.id); + input.updateGroup(group.id); } return input; } @@ -174,20 +198,45 @@ export class CustomEditorService implements ICustomEditorService { if (group) { const existingEditors = group.editors.filter(editor => editor.getResource() && isEqual(editor.getResource()!, resource)); if (existingEditors.length) { - await this.editorService.replaceEditors([{ - editor: existingEditors[0], - replacement: input, - options: options ? EditorOptions.create(options) : undefined, - }], group); + const existing = existingEditors[0]; + if (!input.matches(existing)) { + await this.editorService.replaceEditors([{ + editor: existing, + replacement: input, + options: options ? EditorOptions.create(options) : undefined, + }], group); + + if (existing instanceof CustomFileEditorInput) { + existing.dispose(); + } + } } } return this.editorService.openEditor(input, options, group); } + + private updateContext() { + const activeControl = this.editorService.activeControl; + if (!activeControl) { + this._hasCustomEditor.reset(); + return; + } + const resource = activeControl.input.getResource(); + if (!resource) { + this._hasCustomEditor.reset(); + return; + } + const possibleEditors = [ + ...this.getContributedCustomEditors(resource), + ...this.getUserConfiguredCustomEditors(resource), + ]; + this._hasCustomEditor.set(possibleEditors.length > 0); + } } export const customEditorsAssociationsKey = 'workbench.experimental.editorAssociations'; -export type CustomEditorsAssociations = readonly (CustomEditorSelector & { readonly viewType: string })[]; +export type CustomEditorsAssociations = readonly (CustomEditorSelector & { readonly viewType: string; })[]; export class CustomEditorContribution implements IWorkbenchContribution { constructor( @@ -203,7 +252,9 @@ export class CustomEditorContribution implements IWorkbenchContribution { group: IEditorGroup ): IOpenEditorOverride | undefined { if (editor instanceof CustomFileEditorInput) { - return undefined; + if (editor.group === group.id) { + return undefined; + } } if (editor instanceof DiffEditorInput) { @@ -224,36 +275,33 @@ export class CustomEditorContribution implements IWorkbenchContribution { group: IEditorGroup ): IOpenEditorOverride | undefined { const userConfiguredEditors = this.customEditorService.getUserConfiguredCustomEditors(resource); - const contributedEditors = this.customEditorService.getContributedCustomEditors(resource); - - if (!userConfiguredEditors.length) { - if (!contributedEditors.length) { - return undefined; // {{SQL CARBON EDIT}} strict-null-check - } - - const defaultEditors = contributedEditors.filter(editor => editor.discretion === CustomEditorDiscretion.default); - if (defaultEditors.length === 1) { - return { - override: this.customEditorService.openWith(resource, defaultEditors[0].id, options, group), - }; - } - } - - for (const input of group.editors) { - if (input instanceof CustomFileEditorInput && isEqual(input.getResource(), resource)) { - return { - override: group.openEditor(input, options).then(withNullAsUndefined) - }; - } - } - if (userConfiguredEditors.length) { return { override: this.customEditorService.openWith(resource, userConfiguredEditors[0].id, options, group), }; } - // Open default editor but prompt user to see if they wish to use a custom one instead + const contributedEditors = this.customEditorService.getContributedCustomEditors(resource); + if (!contributedEditors.length) { + return undefined; // {{SQL CARBON EDIT}} Strict-null-checks + } + + // Find the single default editor to use (if any) by looking at the editor's priority and the + // other contributed editors. + const defaultEditor = find(contributedEditors, editor => { + if (editor.priority !== CustomEditorPriority.default && editor.priority !== CustomEditorPriority.builtin) { + return false; + } + return contributedEditors.every(otherEditor => + otherEditor === editor || isLowerPriority(otherEditor, editor)); + }); + if (defaultEditor) { + return { + override: this.customEditorService.openWith(resource, defaultEditor.id, options, group), + }; + } + + // Open VS Code's standard editor but prompt user to see if they wish to use a custom one instead return { override: (async () => { const standardEditor = await this.editorService.openEditor(editor, { ...options, ignoreOverrides: true }, group); @@ -285,15 +333,20 @@ export class CustomEditorContribution implements IWorkbenchContribution { return undefined; } - const editors = distinct([ - ...this.customEditorService.getUserConfiguredCustomEditors(resource), - ...this.customEditorService.getContributedCustomEditors(resource), - ], editor => editor.id); + // Prefer default editors in the diff editor case but ultimatly always take the first editor + const editors = mergeSort( + distinct([ + ...this.customEditorService.getUserConfiguredCustomEditors(resource), + ...this.customEditorService.getContributedCustomEditors(resource), + ], editor => editor.id), + (a, b) => { + return priorityToRank(a.priority) - priorityToRank(b.priority); + }); if (!editors.length) { return undefined; } - // Always prefer the first editor in the diff editor case + return this.customEditorService.createInput(resource, editors[0].id, group, { customClasses }); }; @@ -313,6 +366,18 @@ export class CustomEditorContribution implements IWorkbenchContribution { } } +function isLowerPriority(otherEditor: CustomEditorInfo, editor: CustomEditorInfo): unknown { + return priorityToRank(otherEditor.priority) < priorityToRank(editor.priority); +} + +function priorityToRank(priority: CustomEditorPriority): number { + switch (priority) { + case CustomEditorPriority.default: return 3; + case CustomEditorPriority.builtin: return 2; + case CustomEditorPriority.option: return 1; + } +} + function matches(selector: CustomEditorSelector, resource: URI): boolean { if (resource.scheme === Schemas.data) { if (!selector.mime) { diff --git a/src/vs/workbench/contrib/customEditor/browser/extensionPoint.ts b/src/vs/workbench/contrib/customEditor/browser/extensionPoint.ts index 86eabe8a2f..446876b942 100644 --- a/src/vs/workbench/contrib/customEditor/browser/extensionPoint.ts +++ b/src/vs/workbench/contrib/customEditor/browser/extensionPoint.ts @@ -5,7 +5,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as nls from 'vs/nls'; -import { CustomEditorDiscretion, CustomEditorSelector } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { CustomEditorPriority, CustomEditorSelector } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { languagesExtPoint } from 'vs/workbench/services/mode/common/workbenchModeService'; @@ -13,14 +13,14 @@ namespace WebviewEditorContribution { export const viewType = 'viewType'; export const displayName = 'displayName'; export const selector = 'selector'; - export const discretion = 'discretion'; + export const priority = 'priority'; } interface IWebviewEditorsExtensionPoint { readonly [WebviewEditorContribution.viewType]: string; readonly [WebviewEditorContribution.displayName]: string; readonly [WebviewEditorContribution.selector]?: readonly CustomEditorSelector[]; - readonly [WebviewEditorContribution.discretion]?: CustomEditorDiscretion; + readonly [WebviewEditorContribution.priority]?: CustomEditorPriority; } const webviewEditorsContribution: IJSONSchema = { @@ -41,7 +41,7 @@ const webviewEditorsContribution: IJSONSchema = { }, [WebviewEditorContribution.displayName]: { type: 'string', - description: nls.localize('contributes.displayName', 'Name of the custom editor displayed to users.'), + description: nls.localize('contributes.displayName', 'Human readable name of the custom editor. This is displayed to users when selecting which editor to use.'), }, [WebviewEditorContribution.selector]: { type: 'array', @@ -53,23 +53,25 @@ const webviewEditorsContribution: IJSONSchema = { type: 'string', description: nls.localize('contributes.selector.filenamePattern', 'Glob that the custom editor is enabled for.'), }, - scheme: { + mime: { type: 'string', - description: nls.localize('contributes.selector.scheme', 'File scheme that the custom editor is enabled for.'), + description: nls.localize('contributes.selector.mime', 'Glob that matches the mime type of a data uri resource.'), } } } }, - [WebviewEditorContribution.discretion]: { + [WebviewEditorContribution.priority]: { type: 'string', - description: nls.localize('contributes.discretion', 'Controls when the custom editor is used. May be overridden by users.'), + description: nls.localize('contributes.priority', 'Controls when the custom editor is used. May be overridden by users.'), enum: [ - CustomEditorDiscretion.default, - CustomEditorDiscretion.option + CustomEditorPriority.default, + CustomEditorPriority.option, + CustomEditorPriority.builtin, ], enumDescriptions: [ - nls.localize('contributes.discretion.default', 'Editor is automatically used for a resource if no other default custom editors are registered for it.'), - nls.localize('contributes.discretion.option', 'Editor is not automatically used but can be selected by a user.'), + nls.localize('contributes.priority.default', 'Editor is automatically used for a resource if no other default custom editors are registered for it.'), + nls.localize('contributes.priority.option', 'Editor is not automatically used but can be selected by a user.'), + nls.localize('contributes.priority.builtin', 'Editor automatically used if no other `default` or `builtin` editors are registered for the resource.'), ], default: 'default' } diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index 0ee3907095..402d01560e 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -8,9 +8,12 @@ import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { EditorInput, IEditor } from 'vs/workbench/common/editor'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export const ICustomEditorService = createDecorator('customEditorService'); +export const CONTEXT_HAS_CUSTOM_EDITORS = new RawContextKey('hasCustomEditors', false); + export interface ICustomEditorService { _serviceBrand: any; @@ -23,8 +26,9 @@ export interface ICustomEditorService { promptOpenWith(resource: URI, options?: ITextEditorOptions, group?: IEditorGroup): Promise; } -export const enum CustomEditorDiscretion { +export const enum CustomEditorPriority { default = 'default', + builtin = 'builtin', option = 'option', } @@ -36,6 +40,6 @@ export interface CustomEditorSelector { export interface CustomEditorInfo { readonly id: string; readonly displayName: string; - readonly discretion: CustomEditorDiscretion; + readonly priority: CustomEditorPriority; readonly selector: readonly CustomEditorSelector[]; } diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 81c0f44202..ad2eb8d43d 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -12,12 +12,12 @@ import { IAction, Action } from 'vs/base/common/actions'; import { Range } from 'vs/editor/common/core/range'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType, IContentWidget, IActiveCodeEditor, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness, ITextModel } from 'vs/editor/common/model'; +import { IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness, ITextModel, OverviewRulerLane, IModelDecorationOverviewRulerOptions } from 'vs/editor/common/model'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { RemoveBreakpointAction } from 'vs/workbench/contrib/debug/browser/debugActions'; -import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, BreakpointWidgetContext, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, IBreakpointUpdateData } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, BreakpointWidgetContext, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, IBreakpointUpdateData, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; import { ContextSubMenu } from 'vs/base/browser/contextmenu'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -30,6 +30,8 @@ import { memoize } from 'vs/base/common/decorators'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { distinct } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; const $ = dom.$; @@ -45,7 +47,7 @@ const breakpointHelperDecoration: IModelDecorationOptions = { stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges }; -function createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArray, debugService: IDebugService): { range: Range; options: IModelDecorationOptions; }[] { +function createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArray, debugService: IDebugService, debugSettings: IDebugConfiguration): { range: Range; options: IModelDecorationOptions; }[] { const result: { range: Range; options: IModelDecorationOptions; }[] = []; breakpoints.forEach((breakpoint) => { if (breakpoint.lineNumber <= model.getLineCount()) { @@ -56,7 +58,7 @@ function createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArr ); result.push({ - options: getBreakpointDecorationOptions(model, breakpoint, debugService), + options: getBreakpointDecorationOptions(model, breakpoint, debugService, debugSettings), range }); } @@ -65,7 +67,7 @@ function createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArr return result; } -function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoint, debugService: IDebugService): IModelDecorationOptions { +function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoint, debugService: IDebugService, debugSettings: IDebugConfiguration): IModelDecorationOptions { const { className, message } = getBreakpointMessageAndClassName(debugService, breakpoint); let glyphMarginHoverMessage: MarkdownString | undefined; @@ -78,11 +80,22 @@ function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoi } } + let overviewRulerDecoration: IModelDecorationOverviewRulerOptions | null; + if (debugSettings.showBreakpointsInOverviewRuler) { + overviewRulerDecoration = { + color: 'rgb(124, 40, 49)', + position: OverviewRulerLane.Left + }; + } else { + overviewRulerDecoration = null; + } + return { glyphMarginClassName: className, glyphMarginHoverMessage, stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - beforeContentClassName: breakpoint.column ? `debug-breakpoint-placeholder` : undefined + beforeContentClassName: breakpoint.column ? `debug-breakpoint-placeholder` : undefined, + overviewRuler: overviewRulerDecoration }; } @@ -138,16 +151,13 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, @IDialogService private readonly dialogService: IDialogService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { this.breakpointWidgetVisible = CONTEXT_BREAKPOINT_WIDGET_VISIBLE.bindTo(contextKeyService); this.registerListeners(); this.setDecorationsScheduler = new RunOnceScheduler(() => this.setDecorations(), 30); } - getId(): string { - return BREAKPOINT_EDITOR_CONTRIBUTION_ID; - } - private registerListeners(): void { this.toDispose.push(this.editor.onMouseDown(async (e: IEditorMouseEvent) => { const data = e.target.detail as IMarginData; @@ -220,7 +230,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { this.toDispose.push(this.editor.onMouseMove((e: IEditorMouseEvent) => { let showBreakpointHintAtLineNumber = -1; const model = this.editor.getModel(); - if (model && e.target.position && e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN && this.debugService.getConfigurationManager().canSetBreakpointsIn(model) && + if (model && e.target.position && (e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN || e.target.type === MouseTargetType.GUTTER_LINE_NUMBERS) && this.debugService.getConfigurationManager().canSetBreakpointsIn(model) && this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) { const data = e.target.detail as IMarginData; if (!data.isAfterLines) { @@ -243,6 +253,11 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { } })); this.toDispose.push(this.editor.onDidChangeModelDecorations(() => this.onModelDecorationsChanged())); + this.toDispose.push(this.configurationService.onDidChangeConfiguration(async (e) => { + if (e.affectsConfiguration('debug.showBreakpointsInOverviewRuler')) { + await this.setDecorations(); + } + })); } private getContextMenuActions(breakpoints: ReadonlyArray, uri: URI, lineNumber: number, column?: number): Array { @@ -305,7 +320,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { nls.localize('addConditionalBreakpoint', "Add Conditional Breakpoint..."), undefined, true, - () => Promise.resolve(this.showBreakpointWidget(lineNumber, column)) + () => Promise.resolve(this.showBreakpointWidget(lineNumber, column, BreakpointWidgetContext.CONDITION)) )); actions.push(new Action( 'addLogPoint', @@ -357,7 +372,8 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { const activeCodeEditor = this.editor; const model = activeCodeEditor.getModel(); const breakpoints = this.debugService.getModel().getBreakpoints({ uri: model.uri }); - const desiredBreakpointDecorations = createBreakpointDecorations(model, breakpoints, this.debugService); + const debugSettings = this.configurationService.getValue('debug'); + const desiredBreakpointDecorations = createBreakpointDecorations(model, breakpoints, this.debugService, debugSettings); try { this.ignoreDecorationsChangedEvent = true; @@ -542,6 +558,20 @@ class InlineBreakpointWidget implements IContentWidget, IDisposable { onHide: () => dispose(actions) }); })); + + const updateSize = () => { + const lineHeight = this.editor.getOption(EditorOption.lineHeight); + this.domNode.style.height = `${lineHeight}px`; + this.domNode.style.width = `${Math.ceil(0.8 * lineHeight)}px`; + this.domNode.style.marginLeft = `${Math.ceil(0.35 * lineHeight)}px`; + }; + updateSize(); + + this.toDispose.push(this.editor.onDidChangeConfiguration(c => { + if (c.hasChanged(EditorOption.fontSize) || c.hasChanged(EditorOption.lineHeight)) { + updateSize(); + } + })); } @memoize @@ -572,4 +602,4 @@ class InlineBreakpointWidget implements IContentWidget, IDisposable { } } -registerEditorContribution(BreakpointEditorContribution); +registerEditorContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID, BreakpointEditorContribution); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 9249e04a1b..ed7f67dab2 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -54,8 +54,9 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi private hitCountInput = ''; private logMessageInput = ''; private breakpoint: IBreakpoint | undefined; + private context: Context; - constructor(editor: ICodeEditor, private lineNumber: number, private column: number | undefined, private context: Context, + constructor(editor: ICodeEditor, private lineNumber: number, private column: number | undefined, context: Context | undefined, @IContextViewService private readonly contextViewService: IContextViewService, @IDebugService private readonly debugService: IDebugService, @IThemeService private readonly themeService: IThemeService, @@ -74,7 +75,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi this.breakpoint = breakpoints.length ? breakpoints[0] : undefined; } - if (this.context === undefined) { + if (context === undefined) { if (this.breakpoint && !this.breakpoint.condition && !this.breakpoint.hitCondition && this.breakpoint.logMessage) { this.context = Context.LOG_MESSAGE; } else if (this.breakpoint && !this.breakpoint.condition && this.breakpoint.hitCondition) { @@ -82,6 +83,8 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi } else { this.context = Context.CONDITION; } + } else { + this.context = context; } this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(e => { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 50e03fb6fd..dd408218ee 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -14,7 +14,7 @@ import { IContextMenuService, IContextViewService } from 'vs/platform/contextvie import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { Constants } from 'vs/editor/common/core/uint'; +import { Constants } from 'vs/base/common/uint'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IListVirtualDelegate, IListContextMenuEvent, IListRenderer } from 'vs/base/browser/ui/list/list'; @@ -161,21 +161,19 @@ export class BreakpointsView extends ViewletPanel { const breakpointType = element instanceof Breakpoint && element.logMessage ? nls.localize('Logpoint', "Logpoint") : nls.localize('Breakpoint', "Breakpoint"); if (element instanceof Breakpoint || element instanceof FunctionBreakpoint) { - actions.push(new Action('workbench.action.debug.openEditorAndEditBreakpoint', nls.localize('editBreakpoint', "Edit {0}...", breakpointType), '', true, () => { + actions.push(new Action('workbench.action.debug.openEditorAndEditBreakpoint', nls.localize('editBreakpoint', "Edit {0}...", breakpointType), '', true, async () => { if (element instanceof Breakpoint) { - return openBreakpointSource(element, false, false, this.debugService, this.editorService).then(editor => { - if (editor) { - const codeEditor = editor.getControl(); - if (isCodeEditor(codeEditor)) { - codeEditor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(element.lineNumber, element.column); - } + const editor = await openBreakpointSource(element, false, false, this.debugService, this.editorService); + if (editor) { + const codeEditor = editor.getControl(); + if (isCodeEditor(codeEditor)) { + codeEditor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(element.lineNumber, element.column); } - }); + } + } else { + this.debugService.getViewModel().setSelectedFunctionBreakpoint(element); + this.onBreakpointsChange(); } - - this.debugService.getViewModel().setSelectedFunctionBreakpoint(element); - this.onBreakpointsChange(); - return Promise.resolve(undefined); })); actions.push(new Separator()); } diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 3499c5de7c..7c4f72c6f7 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -34,6 +34,7 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { isSessionAttach } from 'vs/workbench/contrib/debug/common/debugUtils'; import { STOP_ID, STOP_LABEL, DISCONNECT_ID, DISCONNECT_LABEL, RESTART_SESSION_ID, RESTART_LABEL, STEP_OVER_ID, STEP_OVER_LABEL, STEP_INTO_LABEL, STEP_INTO_ID, STEP_OUT_LABEL, STEP_OUT_ID, PAUSE_ID, PAUSE_LABEL, CONTINUE_ID, CONTINUE_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { ICommandService } from 'vs/platform/commands/common/commands'; +import { CollapseAction } from 'vs/workbench/browser/viewlet'; const $ = dom.$; @@ -82,8 +83,16 @@ export class CallStackView extends ViewletPanel { this.pauseMessageLabel.title = thread.stoppedDetails.text || ''; dom.toggleClass(this.pauseMessageLabel, 'exception', thread.stoppedDetails.reason === 'exception'); this.pauseMessage.hidden = false; + if (this.toolbar) { + this.toolbar.setActions([])(); + } + } else { this.pauseMessage.hidden = true; + if (this.toolbar) { + const collapseAction = new CollapseAction(this.tree, true, 'explorer-action collapse-explorer'); + this.toolbar.setActions([collapseAction])(); + } } this.needsRefresh = false; diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index bd1d086f7f..ff9b3c05f3 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -262,8 +262,14 @@ configurationRegistry.registerConfiguration({ }, 'debug.onTaskErrors': { enum: ['debugAnyway', 'showErrors', 'prompt'], + enumDescriptions: [nls.localize('debugAnyway', "Ignore task errors and start debugging."), nls.localize('showErrors', "Show the Problems view and do not start debugging."), nls.localize('prompt', "Prompt user.")], description: nls.localize('debug.onTaskErrors', "Controls what to do when errors are encountered after running a preLaunchTask."), default: 'prompt' + }, + 'debug.showBreakpointsInOverviewRuler': { + type: 'boolean', + description: nls.localize({ comment: ['This is the description for a setting'], key: 'showBreakpointsInOverviewRuler' }, "Controls whether breakpoints should be shown in the overview ruler."), + default: false } } }); diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index fcc6469ecd..61d3025175 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -36,7 +36,7 @@ export class StartDebugActionViewItem implements IActionViewItem { private selected = 0; constructor( - private context: any, + private context: unknown, private action: IAction, @IDebugService private readonly debugService: IDebugService, @IThemeService private readonly themeService: IThemeService, @@ -122,8 +122,8 @@ export class StartDebugActionViewItem implements IActionViewItem { } })); this.toDispose.push(attachStylerCallback(this.themeService, { selectBorder }, colors => { - this.container.style.border = colors.selectBorder ? `1px solid ${colors.selectBorder}` : null; - selectBoxContainer.style.borderLeft = colors.selectBorder ? `1px solid ${colors.selectBorder}` : null; + this.container.style.border = colors.selectBorder ? `1px solid ${colors.selectBorder}` : ''; + selectBoxContainer.style.borderLeft = colors.selectBorder ? `1px solid ${colors.selectBorder}` : ''; })); this.updateOptions(); diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index 19ea42083a..05fb2da0e6 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -12,23 +12,17 @@ import { Variable, Breakpoint, FunctionBreakpoint } from 'vs/workbench/contrib/d import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { startDebugging } from 'vs/workbench/contrib/debug/common/debugUtils'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; export abstract class AbstractDebugAction extends Action { - protected toDispose: IDisposable[]; - constructor( id: string, label: string, cssClass: string, @IDebugService protected debugService: IDebugService, @IKeybindingService protected keybindingService: IKeybindingService, ) { super(id, label, cssClass, false); - this.toDispose = []; - this.toDispose.push(this.debugService.onDidChangeState(state => this.updateEnablement(state))); + this._register(this.debugService.onDidChangeState(state => this.updateEnablement(state))); this.updateLabel(label); this.updateEnablement(); @@ -56,16 +50,11 @@ export abstract class AbstractDebugAction extends Action { protected isEnabled(_: State): boolean { return true; } - - dispose(): void { - super.dispose(); - this.toDispose = dispose(this.toDispose); - } } export class ConfigureAction extends AbstractDebugAction { static readonly ID = 'workbench.action.debug.configure'; - static LABEL = nls.localize('openLaunchJson', "Open {0}", 'launch.json'); + static readonly LABEL = nls.localize('openLaunchJson', "Open {0}", 'launch.json'); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @@ -74,7 +63,7 @@ export class ConfigureAction extends AbstractDebugAction { @IWorkspaceContextService private readonly contextService: IWorkspaceContextService ) { super(id, label, 'debug-action configure', debugService, keybindingService); - this.toDispose.push(debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateClass())); + this._register(debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateClass())); this.updateClass(); } @@ -92,19 +81,17 @@ export class ConfigureAction extends AbstractDebugAction { this.class = configurationCount > 0 ? 'debug-action configure' : 'debug-action configure notification'; } - run(event?: any): Promise { + async run(event?: any): Promise { if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { this.notificationService.info(nls.localize('noFolderDebugConfig', "Please first open a folder in order to do advanced debug configuration.")); - return Promise.resolve(); + return; } const sideBySide = !!(event && (event.ctrlKey || event.metaKey)); const configurationManager = this.debugService.getConfigurationManager(); - if (!configurationManager.selectedConfiguration.launch) { - configurationManager.selectConfiguration(configurationManager.getLaunches()[0]); + if (configurationManager.selectedConfiguration.launch) { + return configurationManager.selectedConfiguration.launch.openConfigFile(sideBySide, false); } - - return configurationManager.selectedConfiguration.launch!.openConfigFile(sideBySide, false); } } @@ -116,18 +103,18 @@ export class StartAction extends AbstractDebugAction { @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IHistoryService private readonly historyService: IHistoryService ) { super(id, label, 'debug-action start', debugService, keybindingService); - this.toDispose.push(this.debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateEnablement())); - this.toDispose.push(this.debugService.onDidNewSession(() => this.updateEnablement())); - this.toDispose.push(this.debugService.onDidEndSession(() => this.updateEnablement())); - this.toDispose.push(this.contextService.onDidChangeWorkbenchState(() => this.updateEnablement())); + this._register(this.debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateEnablement())); + this._register(this.debugService.onDidNewSession(() => this.updateEnablement())); + this._register(this.debugService.onDidEndSession(() => this.updateEnablement())); + this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateEnablement())); } run(): Promise { - return startDebugging(this.debugService, this.historyService, this.isNoDebug()); + const { launch, name } = this.debugService.getConfigurationManager().selectedConfiguration; + return this.debugService.startDebugging(launch, name, { noDebug: this.isNoDebug() }); } protected isNoDebug(): boolean { @@ -165,7 +152,7 @@ export class RunAction extends StartAction { export class SelectAndStartAction extends AbstractDebugAction { static readonly ID = 'workbench.action.debug.selectandstart'; - static LABEL = nls.localize('selectAndStartDebugging', "Select and Start Debugging"); + static readonly LABEL = nls.localize('selectAndStartDebugging', "Select and Start Debugging"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @@ -182,7 +169,7 @@ export class SelectAndStartAction extends AbstractDebugAction { export class RemoveBreakpointAction extends Action { static readonly ID = 'workbench.debug.viewlet.action.removeBreakpoint'; - static LABEL = nls.localize('removeBreakpoint', "Remove Breakpoint"); + static readonly LABEL = nls.localize('removeBreakpoint', "Remove Breakpoint"); constructor(id: string, label: string, @IDebugService private readonly debugService: IDebugService) { super(id, label, 'debug-action remove'); @@ -196,11 +183,11 @@ export class RemoveBreakpointAction extends Action { export class RemoveAllBreakpointsAction extends AbstractDebugAction { static readonly ID = 'workbench.debug.viewlet.action.removeAllBreakpoints'; - static LABEL = nls.localize('removeAllBreakpoints', "Remove All Breakpoints"); + static readonly LABEL = nls.localize('removeAllBreakpoints', "Remove All Breakpoints"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { super(id, label, 'debug-action remove-all', debugService, keybindingService); - this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); + this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); } run(): Promise { @@ -215,11 +202,11 @@ export class RemoveAllBreakpointsAction extends AbstractDebugAction { export class EnableAllBreakpointsAction extends AbstractDebugAction { static readonly ID = 'workbench.debug.viewlet.action.enableAllBreakpoints'; - static LABEL = nls.localize('enableAllBreakpoints', "Enable All Breakpoints"); + static readonly LABEL = nls.localize('enableAllBreakpoints', "Enable All Breakpoints"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { super(id, label, 'debug-action enable-all-breakpoints', debugService, keybindingService); - this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); + this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); } run(): Promise { @@ -234,11 +221,11 @@ export class EnableAllBreakpointsAction extends AbstractDebugAction { export class DisableAllBreakpointsAction extends AbstractDebugAction { static readonly ID = 'workbench.debug.viewlet.action.disableAllBreakpoints'; - static LABEL = nls.localize('disableAllBreakpoints', "Disable All Breakpoints"); + static readonly LABEL = nls.localize('disableAllBreakpoints', "Disable All Breakpoints"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { super(id, label, 'debug-action disable-all-breakpoints', debugService, keybindingService); - this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); + this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); } run(): Promise { @@ -253,14 +240,14 @@ export class DisableAllBreakpointsAction extends AbstractDebugAction { export class ToggleBreakpointsActivatedAction extends AbstractDebugAction { static readonly ID = 'workbench.debug.viewlet.action.toggleBreakpointsActivatedAction'; - static ACTIVATE_LABEL = nls.localize('activateBreakpoints', "Activate Breakpoints"); - static DEACTIVATE_LABEL = nls.localize('deactivateBreakpoints', "Deactivate Breakpoints"); + static readonly ACTIVATE_LABEL = nls.localize('activateBreakpoints', "Activate Breakpoints"); + static readonly DEACTIVATE_LABEL = nls.localize('deactivateBreakpoints', "Deactivate Breakpoints"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { super(id, label, 'debug-action breakpoints-activate', debugService, keybindingService); this.updateLabel(this.debugService.getModel().areBreakpointsActivated() ? ToggleBreakpointsActivatedAction.DEACTIVATE_LABEL : ToggleBreakpointsActivatedAction.ACTIVATE_LABEL); - this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => { + this._register(this.debugService.getModel().onDidChangeBreakpoints(() => { this.updateLabel(this.debugService.getModel().areBreakpointsActivated() ? ToggleBreakpointsActivatedAction.DEACTIVATE_LABEL : ToggleBreakpointsActivatedAction.ACTIVATE_LABEL); this.updateEnablement(); })); @@ -277,11 +264,11 @@ export class ToggleBreakpointsActivatedAction extends AbstractDebugAction { export class ReapplyBreakpointsAction extends AbstractDebugAction { static readonly ID = 'workbench.debug.viewlet.action.reapplyBreakpointsAction'; - static LABEL = nls.localize('reapplyAllBreakpoints', "Reapply All Breakpoints"); + static readonly LABEL = nls.localize('reapplyAllBreakpoints', "Reapply All Breakpoints"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { super(id, label, '', debugService, keybindingService); - this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); + this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); } run(): Promise { @@ -297,16 +284,15 @@ export class ReapplyBreakpointsAction extends AbstractDebugAction { export class AddFunctionBreakpointAction extends AbstractDebugAction { static readonly ID = 'workbench.debug.viewlet.action.addFunctionBreakpointAction'; - static LABEL = nls.localize('addFunctionBreakpoint', "Add Function Breakpoint"); + static readonly LABEL = nls.localize('addFunctionBreakpoint', "Add Function Breakpoint"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { super(id, label, 'debug-action add-function-breakpoint', debugService, keybindingService); - this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); + this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); } - run(): Promise { + async run(): Promise { this.debugService.addFunctionBreakpoint(); - return Promise.resolve(); } protected isEnabled(_: State): boolean { @@ -317,17 +303,16 @@ export class AddFunctionBreakpointAction extends AbstractDebugAction { export class AddWatchExpressionAction extends AbstractDebugAction { static readonly ID = 'workbench.debug.viewlet.action.addWatchExpression'; - static LABEL = nls.localize('addWatchExpression', "Add Expression"); + static readonly LABEL = nls.localize('addWatchExpression', "Add Expression"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { super(id, label, 'debug-action add-watch-expression', debugService, keybindingService); - this.toDispose.push(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement())); - this.toDispose.push(this.debugService.getViewModel().onDidSelectExpression(() => this.updateEnablement())); + this._register(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement())); + this._register(this.debugService.getViewModel().onDidSelectExpression(() => this.updateEnablement())); } - run(): Promise { + async run(): Promise { this.debugService.addWatchExpression(); - return Promise.resolve(undefined); } protected isEnabled(_: State): boolean { @@ -338,16 +323,15 @@ export class AddWatchExpressionAction extends AbstractDebugAction { export class RemoveAllWatchExpressionsAction extends AbstractDebugAction { static readonly ID = 'workbench.debug.viewlet.action.removeAllWatchExpressions'; - static LABEL = nls.localize('removeAllWatchExpressions', "Remove All Expressions"); + static readonly LABEL = nls.localize('removeAllWatchExpressions', "Remove All Expressions"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { super(id, label, 'debug-action remove-all', debugService, keybindingService); - this.toDispose.push(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement())); + this._register(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement())); } - run(): Promise { + async run(): Promise { this.debugService.removeWatchExpressions(); - return Promise.resolve(); } protected isEnabled(_: State): boolean { @@ -357,7 +341,7 @@ export class RemoveAllWatchExpressionsAction extends AbstractDebugAction { export class FocusSessionAction extends AbstractDebugAction { static readonly ID = 'workbench.action.debug.focusProcess'; - static LABEL = nls.localize('focusSession', "Focus Session"); + static readonly LABEL = nls.localize('focusSession', "Focus Session"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @@ -367,23 +351,21 @@ export class FocusSessionAction extends AbstractDebugAction { super(id, label, '', debugService, keybindingService); } - run(session: IDebugSession): Promise { - this.debugService.focusStackFrame(undefined, undefined, session, true); + async run(session: IDebugSession): Promise { + await this.debugService.focusStackFrame(undefined, undefined, session, true); const stackFrame = this.debugService.getViewModel().focusedStackFrame; if (stackFrame) { - return stackFrame.openInEditor(this.editorService, true); + await stackFrame.openInEditor(this.editorService, true); } - - return Promise.resolve(undefined); } } export class CopyValueAction extends Action { static readonly ID = 'workbench.debug.viewlet.action.copyValue'; - static LABEL = nls.localize('copyValue', "Copy Value"); + static readonly LABEL = nls.localize('copyValue', "Copy Value"); constructor( - id: string, label: string, private value: any, private context: string, + id: string, label: string, private value: Variable | string, private context: string, @IDebugService private readonly debugService: IDebugService, @IClipboardService private readonly clipboardService: IClipboardService ) { @@ -395,15 +377,19 @@ export class CopyValueAction extends Action { const stackFrame = this.debugService.getViewModel().focusedStackFrame; const session = this.debugService.getViewModel().focusedSession; - if (this.value instanceof Variable && stackFrame && session && this.value.evaluateName) { + if (typeof this.value === 'string') { + return this.clipboardService.writeText(this.value); + } + + if (stackFrame && session && this.value.evaluateName) { try { const evaluation = await session.evaluate(this.value.evaluateName, stackFrame.frameId, this.context); this.clipboardService.writeText(evaluation.body.result); } catch (e) { this.clipboardService.writeText(this.value.value); } + } else { + this.clipboardService.writeText(this.value.value); } - - return this.clipboardService.writeText(this.value); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts b/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts index 2d94e97335..c9d96da1c2 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Constants } from 'vs/editor/common/core/uint'; +import { Constants } from 'vs/base/common/uint'; import { Range } from 'vs/editor/common/core/range'; import { ITextModel, TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions } from 'vs/editor/common/model'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 9e7385b804..f79ccb922d 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -27,8 +27,6 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { startDebugging } from 'vs/workbench/contrib/debug/common/debugUtils'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; @@ -60,10 +58,10 @@ export const DISCONNECT_LABEL = nls.localize('disconnect', "Disconnect"); export const STOP_LABEL = nls.localize('stop', "Stop"); export const CONTINUE_LABEL = nls.localize('continueDebug', "Continue"); -function getThreadAndRun(accessor: ServicesAccessor, threadId: number | undefined, run: (thread: IThread) => Promise): void { +async function getThreadAndRun(accessor: ServicesAccessor, threadId: number | any, run: (thread: IThread) => Promise): Promise { const debugService = accessor.get(IDebugService); let thread: IThread | undefined; - if (threadId) { + if (typeof threadId === 'number') { debugService.getModel().getSessions().forEach(s => { if (!thread) { thread = s.getThread(threadId); @@ -79,7 +77,7 @@ function getThreadAndRun(accessor: ServicesAccessor, threadId: number | undefine } if (thread) { - run(thread).then(undefined, onUnexpectedError); + await run(thread); } } @@ -104,6 +102,10 @@ function getFrame(debugService: IDebugService, frameId: string | undefined): ISt export function registerCommands(): void { + // These commands are used in call stack context menu, call stack inline actions, command pallete, debug toolbar, mac native touch bar + // When the command is exectued in the context of a thread(context menu on a thread, inline call stack action) we pass the thread id + // Otherwise when it is executed "globaly"(using the touch bar, debug toolbar, command pallete) we do not pass any id and just take whatever is the focussed thread + // Same for stackFrame commands and session commands. CommandsRegistry.registerCommand({ id: COPY_STACK_TRACE_ID, handler: async (accessor: ServicesAccessor, _: string, frameId: string | undefined) => { @@ -119,21 +121,21 @@ export function registerCommands(): void { CommandsRegistry.registerCommand({ id: REVERSE_CONTINUE_ID, - handler: (accessor: ServicesAccessor, threadId: number | undefined) => { + handler: (accessor: ServicesAccessor, threadId: number | any) => { getThreadAndRun(accessor, threadId, thread => thread.reverseContinue()); } }); CommandsRegistry.registerCommand({ id: STEP_BACK_ID, - handler: (accessor: ServicesAccessor, threadId: number | undefined) => { + handler: (accessor: ServicesAccessor, threadId: number | any) => { getThreadAndRun(accessor, threadId, thread => thread.stepBack()); } }); CommandsRegistry.registerCommand({ id: TERMINATE_THREAD_ID, - handler: (accessor: ServicesAccessor, threadId: number | undefined) => { + handler: (accessor: ServicesAccessor, threadId: number | any) => { getThreadAndRun(accessor, threadId, thread => thread.terminate()); } }); @@ -199,8 +201,8 @@ export function registerCommands(): void { } if (!session) { - const historyService = accessor.get(IHistoryService); - startDebugging(debugService, historyService, false); + const { launch, name } = debugService.getConfigurationManager().selectedConfiguration; + debugService.startDebugging(launch, name, { noDebug: false }); } else { session.removeReplExpressions(); debugService.restartSession(session).then(undefined, onUnexpectedError); @@ -213,7 +215,7 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.F10, when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'), - handler: (accessor: ServicesAccessor, threadId: number) => { + handler: (accessor: ServicesAccessor, threadId: number | any) => { getThreadAndRun(accessor, threadId, (thread: IThread) => thread.next()); } }); @@ -223,7 +225,7 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib + 10, // Have a stronger weight to have priority over full screen when debugging primary: KeyCode.F11, when: CONTEXT_IN_DEBUG_MODE, - handler: (accessor: ServicesAccessor, threadId: number) => { + handler: (accessor: ServicesAccessor, threadId: number | any) => { getThreadAndRun(accessor, threadId, (thread: IThread) => thread.stepIn()); } }); @@ -233,7 +235,7 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Shift | KeyCode.F11, when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'), - handler: (accessor: ServicesAccessor, threadId: number) => { + handler: (accessor: ServicesAccessor, threadId: number | any) => { getThreadAndRun(accessor, threadId, (thread: IThread) => thread.stepOut()); } }); @@ -243,7 +245,7 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.F6, when: CONTEXT_DEBUG_STATE.isEqualTo('running'), - handler: (accessor: ServicesAccessor, threadId: number) => { + handler: (accessor: ServicesAccessor, threadId: number | any) => { getThreadAndRun(accessor, threadId, thread => thread.pause()); } }); @@ -292,7 +294,7 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.F5, when: CONTEXT_IN_DEBUG_MODE, - handler: (accessor: ServicesAccessor, threadId: number | undefined) => { + handler: (accessor: ServicesAccessor, threadId: number | any) => { getThreadAndRun(accessor, threadId, thread => thread.continue()); } }); @@ -445,14 +447,11 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, when: undefined, primary: undefined, - handler: (accessor) => { + handler: async (accessor) => { const viewletService = accessor.get(IViewletService); - return viewletService.openViewlet(EXTENSIONS_VIEWLET_ID, true) - .then(viewlet => viewlet as IExtensionsViewlet) - .then(viewlet => { - viewlet.search('tag:debuggers @sort:installs'); - viewlet.focus(); - }); + const viewlet = await viewletService.openViewlet(EXTENSIONS_VIEWLET_ID, true) as IExtensionsViewlet; + viewlet.search('tag:debuggers @sort:installs'); + viewlet.focus(); } }); @@ -461,24 +460,23 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, when: undefined, primary: undefined, - handler: (accessor, launchUri: string) => { + handler: async (accessor, launchUri: string) => { const manager = accessor.get(IDebugService).getConfigurationManager(); if (accessor.get(IWorkspaceContextService).getWorkbenchState() === WorkbenchState.EMPTY) { accessor.get(INotificationService).info(nls.localize('noFolderDebugConfig', "Please first open a folder in order to do advanced debug configuration.")); - return undefined; + return; } - const launch = manager.getLaunches().filter(l => l.uri.toString() === launchUri).pop() || manager.selectedConfiguration.launch; - return launch!.openConfigFile(false, false).then(({ editor, created }) => { + const launch = manager.getLaunches().filter(l => l.uri.toString() === launchUri).pop() || manager.selectedConfiguration.launch; + if (launch) { + const { editor, created } = await launch.openConfigFile(false, false); if (editor && !created) { const codeEditor = editor.getControl(); if (codeEditor) { - return codeEditor.getContribution(EDITOR_CONTRIBUTION_ID).addLaunchConfiguration(); + await codeEditor.getContribution(EDITOR_CONTRIBUTION_ID).addLaunchConfiguration(); } } - - return undefined; - }); + } } }); diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 1a1a8c3461..3b0c83a31f 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -13,7 +13,6 @@ import * as resources from 'vs/base/common/resources'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { ITextModel } from 'vs/editor/common/model'; import { IEditor } from 'vs/workbench/common/editor'; -import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -21,7 +20,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IDebugService } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory } from 'vs/workbench/contrib/debug/common/debug'; import { Debugger } from 'vs/workbench/contrib/debug/common/debugger'; import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -32,10 +31,12 @@ import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/plat import { launchSchema, debuggersExtPoint, breakpointsExtPoint } from 'vs/workbench/contrib/debug/common/debugSchemas'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { onUnexpectedError } from 'vs/base/common/errors'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { CancellationToken } from 'vs/base/common/cancellation'; import { withUndefinedAsNull } from 'vs/base/common/types'; +import { sequence } from 'vs/base/common/async'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { first } from 'vs/base/common/arrays'; const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); jsonRegistry.registerSchema(launchSchemaId, launchSchema); @@ -57,7 +58,6 @@ export class ConfigurationManager implements IConfigurationManager { private debugConfigurationTypeContext: IContextKey; constructor( - private debugService: IDebugService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService, @@ -65,21 +65,29 @@ export class ConfigurationManager implements IConfigurationManager { @IInstantiationService private readonly instantiationService: IInstantiationService, @ICommandService private readonly commandService: ICommandService, @IStorageService private readonly storageService: IStorageService, - @ILifecycleService lifecycleService: ILifecycleService, @IExtensionService private readonly extensionService: IExtensionService, - @IContextKeyService contextKeyService: IContextKeyService + @IContextKeyService contextKeyService: IContextKeyService, + @IHistoryService historyService: IHistoryService ) { this.configProviders = []; this.adapterDescriptorFactories = []; this.debuggers = []; this.toDispose = []; this.initLaunches(); - this.registerListeners(lifecycleService); + this.registerListeners(); 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); if (previousSelectedLaunch) { this.selectConfiguration(previousSelectedLaunch, this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE)); + } else if (this.launches.length > 0) { + const rootUri = historyService.getLastActiveWorkspaceRoot(); + let launch = this.getLaunch(rootUri); + if (!launch || launch.getConfigurationNames().length === 0) { + launch = first(this.launches, l => !!(l && l.getConfigurationNames().length), launch) || this.launches[0]; + } + + this.selectConfiguration(launch); } } @@ -182,31 +190,27 @@ export class ConfigurationManager implements IConfigurationManager { return providers.length > 0; } - resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: IConfig, token: CancellationToken): Promise { - return this.activateDebuggers('onDebugResolve', type).then(() => { - // pipe the config through the promises sequentially. Append at the end the '*' types - const providers = this.configProviders.filter(p => p.type === type && p.resolveDebugConfiguration) - .concat(this.configProviders.filter(p => p.type === '*' && p.resolveDebugConfiguration)); + async resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, config: IConfig, token: CancellationToken): Promise { + await this.activateDebuggers('onDebugResolve', type); + // pipe the config through the promises sequentially. Append at the end the '*' types + const providers = this.configProviders.filter(p => p.type === type && p.resolveDebugConfiguration) + .concat(this.configProviders.filter(p => p.type === '*' && p.resolveDebugConfiguration)); - return providers.reduce((promise, provider) => { - return promise.then(config => { - if (config) { - return provider.resolveDebugConfiguration!(folderUri, config, token); - } else { - return Promise.resolve(config); - } - }); - }, Promise.resolve(debugConfiguration)); - }); + await sequence(providers.map(provider => async () => { + config = (await provider.resolveDebugConfiguration!(folderUri, config, token)) || config; + })); + + return config; } - provideDebugConfigurations(folderUri: uri | undefined, type: string, token: CancellationToken): Promise { - return this.activateDebuggers('onDebugInitialConfigurations') - .then(() => Promise.all(this.configProviders.filter(p => p.type === type && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations!(folderUri, token))) - .then(results => results.reduce((first, second) => first.concat(second), []))); + async provideDebugConfigurations(folderUri: uri | undefined, type: string, token: CancellationToken): Promise { + await this.activateDebuggers('onDebugInitialConfigurations'); + const results = await Promise.all(this.configProviders.filter(p => p.type === type && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations!(folderUri, token))); + + return results.reduce((first, second) => first.concat(second), []); } - private registerListeners(lifecycleService: ILifecycleService): void { + private registerListeners(): void { debuggersExtPoint.setHandler((extensions, delta) => { delta.added.forEach(added => { added.value.forEach(rawAdapter => { @@ -242,12 +246,6 @@ export class ConfigurationManager implements IConfigurationManager { delta.removed.forEach(removed => { const removedTypes = removed.value.map(rawAdapter => rawAdapter.type); this.debuggers = this.debuggers.filter(d => removedTypes.indexOf(d.type) === -1); - this.debugService.getModel().getSessions().forEach(s => { - // Stop sessions if their debugger has been removed - if (removedTypes.indexOf(s.configuration.type) >= 0) { - this.debugService.stopSession(s).then(undefined, onUnexpectedError); - } - }); }); // update the schema to include all attributes, snippets and types from extensions. @@ -386,57 +384,54 @@ export class ConfigurationManager implements IConfigurationManager { return this.debuggers.filter(dbg => strings.equalsIgnoreCase(dbg.type, type)).pop(); } - guessDebugger(type?: string): Promise { + async guessDebugger(type?: string): Promise { if (type) { const adapter = this.getDebugger(type); return Promise.resolve(adapter); } const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - let candidates: Promise | undefined; + let candidates: Debugger[] | undefined; if (isCodeEditor(activeTextEditorWidget)) { const model = activeTextEditorWidget.getModel(); const language = model ? model.getLanguageIdentifier().language : undefined; const adapters = this.debuggers.filter(a => language && a.languages && a.languages.indexOf(language) >= 0); if (adapters.length === 1) { - return Promise.resolve(adapters[0]); + return adapters[0]; } if (adapters.length > 1) { - candidates = Promise.resolve(adapters); + candidates = adapters; } } if (!candidates) { - candidates = this.activateDebuggers('onDebugInitialConfigurations').then(() => this.debuggers.filter(dbg => dbg.hasInitialConfiguration() || dbg.hasConfigurationProvider())); + await this.activateDebuggers('onDebugInitialConfigurations'); + candidates = this.debuggers.filter(dbg => dbg.hasInitialConfiguration() || dbg.hasConfigurationProvider()); } - return candidates.then(debuggers => { - debuggers.sort((first, second) => first.label.localeCompare(second.label)); - const picks = debuggers.map(c => ({ label: c.label, debugger: c })); - return this.quickInputService.pick<{ label: string, debugger: Debugger | undefined }>([...picks, { type: 'separator' }, { label: 'More...', debugger: undefined }], { placeHolder: nls.localize('selectDebug', "Select Environment") }) - .then(picked => { - if (picked && picked.debugger) { - return picked.debugger; - } - if (picked) { - this.commandService.executeCommand('debug.installAdditionalDebuggers'); - } - return undefined; - }); - }); + candidates.sort((first, second) => first.label.localeCompare(second.label)); + const picks = candidates.map(c => ({ label: c.label, debugger: c })); + const picked = await this.quickInputService.pick<{ label: string, debugger: Debugger | undefined }>([...picks, { type: 'separator' }, { label: 'More...', debugger: undefined }], { placeHolder: nls.localize('selectDebug', "Select Environment") }); + + if (picked && picked.debugger) { + return picked.debugger; + } + if (picked) { + this.commandService.executeCommand('debug.installAdditionalDebuggers'); + } + + return undefined; } - activateDebuggers(activationEvent: string, debugType?: string): Promise { - const thenables: Promise[] = [ + async activateDebuggers(activationEvent: string, debugType?: string): Promise { + const promises: Promise[] = [ this.extensionService.activateByEvent(activationEvent), this.extensionService.activateByEvent('onDebug') ]; if (debugType) { - thenables.push(this.extensionService.activateByEvent(`${activationEvent}:${debugType}`)); + promises.push(this.extensionService.activateByEvent(`${activationEvent}:${debugType}`)); } - return Promise.all(thenables).then(_ => { - return undefined; - }); + await Promise.all(promises); } private setSelectedLaunchName(selectedName: string | undefined): void { @@ -533,54 +528,57 @@ class Launch extends AbstractLaunch implements ILaunch { return this.configurationService.inspect('launch', { resource: this.workspace.uri }).workspaceFolder; } - openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditor | null, created: boolean }> { + async openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditor | null, created: boolean }> { const resource = this.uri; let created = false; - - return this.fileService.readFile(resource).then(content => content.value, err => { + let content = ''; + try { + const fileContent = await this.fileService.readFile(resource); + content = fileContent.value.toString(); + } catch { // launch.json not found: create one by collecting launch configs from debugConfigProviders - return this.configurationManager.guessDebugger(type).then(adapter => { - if (adapter) { - return this.configurationManager.provideDebugConfigurations(this.workspace.uri, adapter.type, token || CancellationToken.None).then(initialConfigs => { - return adapter.getInitialConfigurationContent(initialConfigs); - }); - } else { - return ''; - } - }).then(content => { - - if (!content) { - return ''; - } + const adapter = await this.configurationManager.guessDebugger(type); + if (adapter) { + const initialConfigs = await this.configurationManager.provideDebugConfigurations(this.workspace.uri, adapter.type, token || CancellationToken.None); + content = await adapter.getInitialConfigurationContent(initialConfigs); + } + if (content) { created = true; // pin only if config file is created #8727 - return this.textFileService.write(resource, content).then(() => content); - }); - }).then(content => { - if (!content) { - return { editor: null, created: false }; - } - const contentValue = content.toString(); - const index = contentValue.indexOf(`"${this.configurationManager.selectedConfiguration.name}"`); - let startLineNumber = 1; - for (let i = 0; i < index; i++) { - if (contentValue.charAt(i) === '\n') { - startLineNumber++; + try { + await this.textFileService.write(resource, content); + } catch (error) { + throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error.message)); } } - const selection = startLineNumber > 1 ? { startLineNumber, startColumn: 4 } : undefined; + } - return Promise.resolve(this.editorService.openEditor({ - resource, - options: { - selection, - preserveFocus, - pinned: created, - revealIfVisible: true - }, - }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => ({ editor: withUndefinedAsNull(editor), created }))); - }, (error: Error) => { - throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error.message)); + if (content === '') { + return { editor: null, created: false }; + } + + const index = content.indexOf(`"${this.configurationManager.selectedConfiguration.name}"`); + let startLineNumber = 1; + for (let i = 0; i < index; i++) { + if (content.charAt(i) === '\n') { + startLineNumber++; + } + } + const selection = startLineNumber > 1 ? { startLineNumber, startColumn: 4 } : undefined; + + const editor = await this.editorService.openEditor({ + resource, + options: { + selection, + preserveFocus, + pinned: created, + revealIfVisible: true + }, + }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + + return ({ + editor: withUndefinedAsNull(editor), + created }); } } @@ -610,11 +608,17 @@ class WorkspaceLaunch extends AbstractLaunch implements ILaunch { return this.configurationService.inspect('launch').workspace; } - openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor | null, created: boolean }> { - return this.editorService.openEditor({ + async openConfigFile(sideBySide: boolean, preserveFocus: boolean): Promise<{ editor: IEditor | null, created: boolean }> { + + const editor = await this.editorService.openEditor({ resource: this.contextService.getWorkspace().configuration!, options: { preserveFocus } - }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => ({ editor: withUndefinedAsNull(editor), created: false })); + }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + + return ({ + editor: withUndefinedAsNull(editor), + created: false + }); } } @@ -647,7 +651,11 @@ class UserLaunch extends AbstractLaunch implements ILaunch { return this.configurationService.inspect('launch').user; } - openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor | null, created: boolean }> { - return this.preferencesService.openGlobalSettings(false, { preserveFocus }).then(editor => ({ editor: withUndefinedAsNull(editor), created: false })); + async openConfigFile(_: boolean, preserveFocus: boolean): Promise<{ editor: IEditor | null, created: boolean }> { + const editor = await this.preferencesService.openGlobalSettings(false, { preserveFocus }); + return ({ + editor: withUndefinedAsNull(editor), + created: false + }); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index 6c391d61ce..b25e36fa23 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -34,7 +34,7 @@ class ToggleBreakpointAction extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { if (editor.hasModel()) { const debugService = accessor.get(IDebugService); const modelUri = editor.getModel().uri; @@ -49,12 +49,10 @@ class ToggleBreakpointAction extends EditorAction { } else if (canSet) { return (debugService.addBreakpoints(modelUri, [{ lineNumber: line }], 'debugEditorActions.toggleBreakpointAction')); } else { - return Promise.resolve([]); + return []; } })); } - - return Promise.resolve(); } } @@ -75,7 +73,7 @@ class ConditionalBreakpointAction extends EditorAction { const position = editor.getPosition(); if (position && editor.hasModel() && debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) { - editor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(position.lineNumber, undefined); + editor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(position.lineNumber, undefined, BreakpointWidgetContext.CONDITION); } } } @@ -97,15 +95,15 @@ class LogPointAction extends EditorAction { const position = editor.getPosition(); if (position && editor.hasModel() && debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) { - editor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(position.lineNumber, BreakpointWidgetContext.LOG_MESSAGE); + editor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(position.lineNumber, position.column, BreakpointWidgetContext.LOG_MESSAGE); } } } export class RunToCursorAction extends EditorAction { - public static ID = 'editor.debug.action.runToCursor'; - public static LABEL = nls.localize('runToCursor', "Run to Cursor"); + public static readonly ID = 'editor.debug.action.runToCursor'; + public static readonly LABEL = nls.localize('runToCursor', "Run to Cursor"); constructor() { super({ @@ -120,11 +118,11 @@ export class RunToCursorAction extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { const debugService = accessor.get(IDebugService); const focusedSession = debugService.getViewModel().focusedSession; if (debugService.state !== State.Stopped || !focusedSession) { - return Promise.resolve(undefined); + return; } let breakpointToRemove: IBreakpoint; @@ -139,18 +137,18 @@ export class RunToCursorAction extends EditorAction { }); const position = editor.getPosition(); - if (!editor.hasModel() || !position) { - return Promise.resolve(); - } - - const uri = editor.getModel().uri; - const bpExists = !!(debugService.getModel().getBreakpoints({ column: position.column, lineNumber: position.lineNumber, uri }).length); - return (bpExists ? Promise.resolve(null) : >debugService.addBreakpoints(uri, [{ lineNumber: position.lineNumber, column: position.column }], 'debugEditorActions.runToCursorAction')).then((breakpoints) => { - if (breakpoints && breakpoints.length) { - breakpointToRemove = breakpoints[0]; + if (editor.hasModel() && position) { + const uri = editor.getModel().uri; + const bpExists = !!(debugService.getModel().getBreakpoints({ column: position.column, lineNumber: position.lineNumber, uri }).length); + if (!bpExists) { + const breakpoints = await debugService.addBreakpoints(uri, [{ lineNumber: position.lineNumber, column: position.column }], 'debugEditorActions.runToCursorAction'); + if (breakpoints && breakpoints.length) { + breakpointToRemove = breakpoints[0]; + } } - debugService.getViewModel().focusedThread!.continue(); - }); + + await debugService.getViewModel().focusedThread!.continue(); + } } } @@ -169,13 +167,13 @@ class SelectionToReplAction extends EditorAction { }); } - public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { const debugService = accessor.get(IDebugService); const panelService = accessor.get(IPanelService); const viewModel = debugService.getViewModel(); const session = viewModel.focusedSession; if (!editor.hasModel() || !session) { - return Promise.resolve(); + return; } const text = editor.getModel().getValueInRange(editor.getSelection()); @@ -199,15 +197,16 @@ class SelectionToWatchExpressionsAction extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { const debugService = accessor.get(IDebugService); const viewletService = accessor.get(IViewletService); if (!editor.hasModel()) { - return Promise.resolve(); + return; } const text = editor.getModel().getValueInRange(editor.getSelection()); - return viewletService.openViewlet(VIEWLET_ID).then(() => debugService.addWatchExpression(text)); + await viewletService.openViewlet(VIEWLET_ID); + debugService.addWatchExpression(text); } } @@ -227,14 +226,14 @@ class ShowDebugHoverAction extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { const position = editor.getPosition(); if (!position || !editor.hasModel()) { - return Promise.resolve(); + return; } const word = editor.getModel().getWordAtPosition(position); if (!word) { - return Promise.resolve(); + return; } const range = new Range(position.lineNumber, position.column, position.lineNumber, word.endColumn); @@ -247,7 +246,7 @@ class GoToBreakpointAction extends EditorAction { super(opts); } - public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): Promise { + async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { const debugService = accessor.get(IDebugService); const editorService = accessor.get(IEditorService); if (editor.hasModel()) { @@ -279,8 +278,6 @@ class GoToBreakpointAction extends EditorAction { return openBreakpointSource(moveBreakpoint, false, true, debugService, editorService); } } - - return Promise.resolve(null); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 420393a5f6..00fe86c75f 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { RunOnceScheduler } from 'vs/base/common/async'; import * as env from 'vs/base/common/platform'; import { visit } from 'vs/base/common/json'; -import { Constants } from 'vs/editor/common/core/uint'; +import { Constants } from 'vs/base/common/uint'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { StandardTokenType } from 'vs/editor/common/modes'; @@ -98,7 +98,7 @@ class DebugEditorContribution implements IDebugEditorContribution { this.wordToLineNumbersMap = undefined; this.updateInlineValuesScheduler.schedule(); })); - this.toDispose.push(this.editor.onDidChangeModel(() => { + this.toDispose.push(this.editor.onDidChangeModel(async () => { const stackFrame = this.debugService.getViewModel().focusedStackFrame; const model = this.editor.getModel(); if (model) { @@ -108,7 +108,7 @@ class DebugEditorContribution implements IDebugEditorContribution { this.hideHoverWidget(); this.updateConfigurationWidgetVisibility(); this.wordToLineNumbersMap = undefined; - this.updateInlineValueDecorations(stackFrame); + await this.updateInlineValueDecorations(stackFrame); })); this.toDispose.push(this.editor.onDidScrollChange(() => this.hideHoverWidget)); this.toDispose.push(this.debugService.onDidChangeState((state: State) => { @@ -141,32 +141,26 @@ class DebugEditorContribution implements IDebugEditorContribution { } } - getId(): string { - return EDITOR_CONTRIBUTION_ID; - } - - showHover(range: Range, focus: boolean): Promise { + async showHover(range: Range, focus: boolean): Promise { const sf = this.debugService.getViewModel().focusedStackFrame; const model = this.editor.getModel(); if (sf && model && sf.source.uri.toString() === model.uri.toString()) { return this.hoverWidget.showAt(range, focus); } - - return Promise.resolve(); } - private onFocusStackFrame(sf: IStackFrame | undefined): void { + private async onFocusStackFrame(sf: IStackFrame | undefined): Promise { const model = this.editor.getModel(); if (model) { this._applyHoverConfiguration(model, sf); if (sf && sf.source.uri.toString() === model.uri.toString()) { - this.toggleExceptionWidget(); + await this.toggleExceptionWidget(); } else { this.hideHoverWidget(); } } - this.updateInlineValueDecorations(sf); + await this.updateInlineValueDecorations(sf); } @memoize @@ -261,7 +255,7 @@ class DebugEditorContribution implements IDebugEditorContribution { // end hover business // exception widget - private toggleExceptionWidget(): void { + private async toggleExceptionWidget(): Promise { // Toggles exception widget based on the state of the current editor model and debug stack frame const model = this.editor.getModel(); const focusedSf = this.debugService.getViewModel().focusedStackFrame; @@ -282,11 +276,10 @@ class DebugEditorContribution implements IDebugEditorContribution { if (this.exceptionWidget && !sameUri) { this.closeExceptionWidget(); } else if (sameUri) { - focusedSf.thread.exceptionInfo.then(exceptionInfo => { - if (exceptionInfo && exceptionSf.range.startLineNumber && exceptionSf.range.startColumn) { - this.showExceptionWidget(exceptionInfo, this.debugService.getViewModel().focusedSession, exceptionSf.range.startLineNumber, exceptionSf.range.startColumn); - } - }); + const exceptionInfo = await focusedSf.thread.exceptionInfo; + if (exceptionInfo && exceptionSf.range.startLineNumber && exceptionSf.range.startColumn) { + this.showExceptionWidget(exceptionInfo, this.debugService.getViewModel().focusedSession, exceptionSf.range.startLineNumber, exceptionSf.range.startColumn); + } } } @@ -320,7 +313,7 @@ class DebugEditorContribution implements IDebugEditorContribution { } } - addLaunchConfiguration(): Promise { + async addLaunchConfiguration(): Promise { /* __GDPR__ "debug/addLaunchConfiguration" : {} */ @@ -328,7 +321,7 @@ class DebugEditorContribution implements IDebugEditorContribution { let configurationsArrayPosition: Position | undefined; const model = this.editor.getModel(); if (!model) { - return Promise.resolve(); + return; } let depthInArray = 0; @@ -351,7 +344,7 @@ class DebugEditorContribution implements IDebugEditorContribution { this.editor.focus(); if (!configurationsArrayPosition) { - return Promise.resolve(); + return; } const insertLine = (position: Position): Promise => { @@ -364,7 +357,8 @@ class DebugEditorContribution implements IDebugEditorContribution { return this.commandService.executeCommand('editor.action.insertLineAfter'); }; - return insertLine(configurationsArrayPosition).then(() => this.commandService.executeCommand('editor.action.triggerSuggest')); + await insertLine(configurationsArrayPosition); + await this.commandService.executeCommand('editor.action.triggerSuggest'); } // Inline Decorations @@ -380,12 +374,12 @@ class DebugEditorContribution implements IDebugEditorContribution { @memoize private get updateInlineValuesScheduler(): RunOnceScheduler { return new RunOnceScheduler( - () => this.updateInlineValueDecorations(this.debugService.getViewModel().focusedStackFrame), + async () => await this.updateInlineValueDecorations(this.debugService.getViewModel().focusedStackFrame), 200 ); } - private updateInlineValueDecorations(stackFrame: IStackFrame | undefined): void { + private async updateInlineValueDecorations(stackFrame: IStackFrame | undefined): Promise { const model = this.editor.getModel(); if (!this.configurationService.getValue('debug').inlineValues || !model || !stackFrame || model.uri.toString() !== stackFrame.source.uri.toString()) { @@ -397,20 +391,20 @@ class DebugEditorContribution implements IDebugEditorContribution { this.removeInlineValuesScheduler.cancel(); - stackFrame.getMostSpecificScopes(stackFrame.range) - // Get all top level children in the scope chain - .then(scopes => Promise.all(scopes.map(scope => scope.getChildren() - .then(children => { - let range = new Range(0, 0, stackFrame.range.startLineNumber, stackFrame.range.startColumn); - if (scope.range) { - range = range.setStartPosition(scope.range.startLineNumber, scope.range.startColumn); - } + const scopes = await stackFrame.getMostSpecificScopes(stackFrame.range); + // Get all top level children in the scope chain + const decorationsPerScope = await Promise.all(scopes.map(async scope => { + const children = await scope.getChildren(); + let range = new Range(0, 0, stackFrame.range.startLineNumber, stackFrame.range.startColumn); + if (scope.range) { + range = range.setStartPosition(scope.range.startLineNumber, scope.range.startColumn); + } - return this.createInlineValueDecorationsInsideRange(children, range, model); - }))).then(decorationsPerScope => { - const allDecorations = decorationsPerScope.reduce((previous, current) => previous.concat(current), []); - this.editor.setDecorations(INLINE_VALUE_DECORATION_KEY, allDecorations); - })); + return this.createInlineValueDecorationsInsideRange(children, range, model); + })); + + const allDecorations = decorationsPerScope.reduce((previous, current) => previous.concat(current), []); + this.editor.setDecorations(INLINE_VALUE_DECORATION_KEY, allDecorations); } private createInlineValueDecorationsInsideRange(expressions: ReadonlyArray, range: Range, model: ITextModel): IDecorationOptions[] { @@ -547,4 +541,4 @@ class DebugEditorContribution implements IDebugEditorContribution { } } -registerEditorContribution(DebugEditorContribution); +registerEditorContribution(EDITOR_CONTRIBUTION_ID, DebugEditorContribution); diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index a292fee6f4..8fe8000748 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -94,12 +94,12 @@ export class DebugHoverWidget implements IContentWidget { if (colors.editorHoverBackground) { this.domNode.style.backgroundColor = colors.editorHoverBackground.toString(); } else { - this.domNode.style.backgroundColor = null; + this.domNode.style.backgroundColor = ''; } if (colors.editorHoverBorder) { this.domNode.style.border = `1px solid ${colors.editorHoverBorder}`; } else { - this.domNode.style.border = null; + this.domNode.style.border = ''; } })); this.toDispose.push(this.tree.onDidChangeContentHeight(() => this.layoutTreeAndContainer())); @@ -174,7 +174,7 @@ export class DebugHoverWidget implements IContentWidget { return this.doShow(pos, expression, focus); } - private static _HOVER_HIGHLIGHT_DECORATION_OPTIONS = ModelDecorationOptions.register({ + private static readonly _HOVER_HIGHLIGHT_DECORATION_OPTIONS = ModelDecorationOptions.register({ className: 'hoverHighlight' }); diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index e15bfd6662..ace68ab0cf 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -24,14 +24,13 @@ import * as debugactions from 'vs/workbench/contrib/debug/browser/debugActions'; import { ConfigurationManager } from 'vs/workbench/contrib/debug/browser/debugConfigurationManager'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; import { ITaskService, ITaskSummary } from 'vs/workbench/contrib/tasks/common/taskService'; -import { TaskError } from 'vs/workbench/contrib/tasks/common/taskSystem'; import { VIEWLET_ID as EXPLORER_VIEWLET_ID } from 'vs/workbench/contrib/files/common/files'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { parse, getFirstFrame } from 'vs/base/common/console'; import { TaskEvent, TaskEventKind, TaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks'; @@ -121,7 +120,7 @@ export class DebugService implements IDebugService { this._onWillNewSession = new Emitter(); this._onDidEndSession = new Emitter(); - this.configurationManager = this.instantiationService.createInstance(ConfigurationManager, this); + this.configurationManager = this.instantiationService.createInstance(ConfigurationManager); this.toDispose.push(this.configurationManager); this.debugType = CONTEXT_DEBUG_TYPE.bindTo(contextKeyService); @@ -144,13 +143,13 @@ export class DebugService implements IDebugService { session.configuration.request = 'attach'; session.configuration.port = event.port; session.setSubId(event.subId); - this.launchOrAttachToSession(session).then(undefined, errors.onUnexpectedError); + this.launchOrAttachToSession(session); } })); this.toDispose.push(this.extensionHostDebugService.onTerminateSession(event => { const session = this.model.getSession(event.sessionId); if (session && session.subId === event.subId) { - session.disconnect().then(undefined, errors.onUnexpectedError); + session.disconnect(); } })); this.toDispose.push(this.extensionHostDebugService.onLogToSession(event => { @@ -253,96 +252,99 @@ export class DebugService implements IDebugService { * main entry point * properly manages compounds, checks for errors and handles the initializing state. */ - startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions): Promise { + async startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions): Promise { this.startInitializingState(); - // make sure to save all files and that the configuration is up to date - return this.extensionService.activateByEvent('onDebug').then(() => { - return this.textFileService.saveAll().then(() => this.configurationService.reloadConfiguration(launch ? launch.workspace : undefined).then(() => { - return this.extensionService.whenInstalledExtensionsRegistered().then(() => { + try { + // make sure to save all files and that the configuration is up to date + await this.extensionService.activateByEvent('onDebug'); + await this.textFileService.saveAll(); + await this.configurationService.reloadConfiguration(launch ? launch.workspace : undefined); + await this.extensionService.whenInstalledExtensionsRegistered(); - let config: IConfig | undefined; - let compound: ICompound | undefined; - if (!configOrName) { - configOrName = this.configurationManager.selectedConfiguration.name; + let config: IConfig | undefined; + let compound: ICompound | undefined; + if (!configOrName) { + configOrName = this.configurationManager.selectedConfiguration.name; + } + if (typeof configOrName === 'string' && launch) { + config = launch.getConfiguration(configOrName); + compound = launch.getCompound(configOrName); + + const sessions = this.model.getSessions(); + const alreadyRunningMessage = nls.localize('configurationAlreadyRunning', "There is already a debug configuration \"{0}\" running.", configOrName); + if (sessions.some(s => s.configuration.name === configOrName && (!launch || !launch.workspace || !s.root || s.root.uri.toString() === launch.workspace.uri.toString()))) { + throw new Error(alreadyRunningMessage); + } + if (compound && compound.configurations && sessions.some(p => compound!.configurations.indexOf(p.configuration.name) !== -1)) { + throw new Error(alreadyRunningMessage); + } + } else if (typeof configOrName !== 'string') { + config = configOrName; + } + + if (compound) { + // we are starting a compound debug, first do some error checking and than start each configuration in the compound + if (!compound.configurations) { + throw new Error(nls.localize({ key: 'compoundMustHaveConfigurations', comment: ['compound indicates a "compounds" configuration item', '"configurations" is an attribute and should not be localized'] }, + "Compound must have \"configurations\" attribute set in order to start multiple configurations.")); + } + + const values = await Promise.all(compound.configurations.map(configData => { + const name = typeof configData === 'string' ? configData : configData.name; + if (name === compound!.name) { + return Promise.resolve(false); } - if (typeof configOrName === 'string' && launch) { - config = launch.getConfiguration(configOrName); - compound = launch.getCompound(configOrName); - const sessions = this.model.getSessions(); - const alreadyRunningMessage = nls.localize('configurationAlreadyRunning', "There is already a debug configuration \"{0}\" running.", configOrName); - if (sessions.some(s => s.configuration.name === configOrName && (!launch || !launch.workspace || !s.root || s.root.uri.toString() === launch.workspace.uri.toString()))) { - return Promise.reject(new Error(alreadyRunningMessage)); + let launchForName: ILaunch | undefined; + if (typeof configData === 'string') { + const launchesContainingName = this.configurationManager.getLaunches().filter(l => !!l.getConfiguration(name)); + if (launchesContainingName.length === 1) { + launchForName = launchesContainingName[0]; + } else if (launch && launchesContainingName.length > 1 && launchesContainingName.indexOf(launch) >= 0) { + // If there are multiple launches containing the configuration give priority to the configuration in the current launch + launchForName = launch; + } else { + throw new Error(launchesContainingName.length === 0 ? nls.localize('noConfigurationNameInWorkspace', "Could not find launch configuration '{0}' in the workspace.", name) + : nls.localize('multipleConfigurationNamesInWorkspace', "There are multiple launch configurations '{0}' in the workspace. Use folder name to qualify the configuration.", name)); } - if (compound && compound.configurations && sessions.some(p => compound!.configurations.indexOf(p.configuration.name) !== -1)) { - return Promise.reject(new Error(alreadyRunningMessage)); + } else if (configData.folder) { + const launchesMatchingConfigData = this.configurationManager.getLaunches().filter(l => l.workspace && l.workspace.name === configData.folder && !!l.getConfiguration(configData.name)); + if (launchesMatchingConfigData.length === 1) { + launchForName = launchesMatchingConfigData[0]; + } else { + throw new Error(nls.localize('noFolderWithName', "Can not find folder with name '{0}' for configuration '{1}' in compound '{2}'.", configData.folder, configData.name, compound!.name)); } - } else if (typeof configOrName !== 'string') { - config = configOrName; } - if (compound) { - // we are starting a compound debug, first do some error checking and than start each configuration in the compound - if (!compound.configurations) { - return Promise.reject(new Error(nls.localize({ key: 'compoundMustHaveConfigurations', comment: ['compound indicates a "compounds" configuration item', '"configurations" is an attribute and should not be localized'] }, - "Compound must have \"configurations\" attribute set in order to start multiple configurations."))); - } + return this.createSession(launchForName, launchForName!.getConfiguration(name), options); + })); - return Promise.all(compound.configurations.map(configData => { - const name = typeof configData === 'string' ? configData : configData.name; - if (name === compound!.name) { - return Promise.resolve(false); - } + const result = values.every(success => !!success); // Compound launch is a success only if each configuration launched successfully + this.endInitializingState(); + return result; + } - let launchForName: ILaunch | undefined; - if (typeof configData === 'string') { - const launchesContainingName = this.configurationManager.getLaunches().filter(l => !!l.getConfiguration(name)); - if (launchesContainingName.length === 1) { - launchForName = launchesContainingName[0]; - } else if (launch && launchesContainingName.length > 1 && launchesContainingName.indexOf(launch) >= 0) { - // If there are multiple launches containing the configuration give priority to the configuration in the current launch - launchForName = launch; - } else { - return Promise.reject(new Error(launchesContainingName.length === 0 ? nls.localize('noConfigurationNameInWorkspace', "Could not find launch configuration '{0}' in the workspace.", name) - : nls.localize('multipleConfigurationNamesInWorkspace', "There are multiple launch configurations '{0}' in the workspace. Use folder name to qualify the configuration.", name))); - } - } else if (configData.folder) { - const launchesMatchingConfigData = this.configurationManager.getLaunches().filter(l => l.workspace && l.workspace.name === configData.folder && !!l.getConfiguration(configData.name)); - if (launchesMatchingConfigData.length === 1) { - launchForName = launchesMatchingConfigData[0]; - } else { - return Promise.reject(new Error(nls.localize('noFolderWithName', "Can not find folder with name '{0}' for configuration '{1}' in compound '{2}'.", configData.folder, configData.name, compound!.name))); - } - } + if (configOrName && !config) { + const message = !!launch ? nls.localize('configMissing', "Configuration '{0}' is missing in 'launch.json'.", typeof configOrName === 'string' ? configOrName : JSON.stringify(configOrName)) : + nls.localize('launchJsonDoesNotExist', "'launch.json' does not exist."); + throw new Error(message); + } - return this.createSession(launchForName, launchForName!.getConfiguration(name), options); - })).then(values => values.every(success => !!success)); // Compound launch is a success only if each configuration launched successfully - } - - if (configOrName && !config) { - const message = !!launch ? nls.localize('configMissing', "Configuration '{0}' is missing in 'launch.json'.", typeof configOrName === 'string' ? configOrName : JSON.stringify(configOrName)) : - nls.localize('launchJsonDoesNotExist', "'launch.json' does not exist."); - return Promise.reject(new Error(message)); - } - - return this.createSession(launch, config, options); - }); - })); - }).then(success => { + const result = await this.createSession(launch, config, options); + this.endInitializingState(); + return result; + } catch (err) { // make sure to get out of initializing state, and propagate the result - this.endInitializingState(); - return success; - }, err => { this.endInitializingState(); return Promise.reject(err); - }); + } } /** * gets the debugger for the type, resolves configurations by providers, substitutes variables and runs prelaunch tasks */ - private createSession(launch: ILaunch | undefined, config: IConfig | undefined, options?: IDebugSessionOptions): Promise { + private async createSession(launch: ILaunch | undefined, config: IConfig | undefined, options?: IDebugSessionOptions): Promise { // We keep the debug type in a separate variable 'type' so that a no-folder config has no attributes. // Storing the type in the config would break extensions that assume that the no-folder case is indicated by an empty config. let type: string | undefined; @@ -358,66 +360,71 @@ export class DebugService implements IDebugService { config!.noDebug = true; } - const debuggerThenable: Promise = type ? Promise.resolve() : this.configurationManager.guessDebugger().then(dbgr => { type = dbgr && dbgr.type; }); - return debuggerThenable.then(() => { - this.initCancellationToken = new CancellationTokenSource(); - return this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config!, this.initCancellationToken.token).then(config => { - // a falsy config indicates an aborted launch - if (config && config.type) { - return this.substituteVariables(launch, config).then(resolvedConfig => { + if (!type) { + const guess = await this.configurationManager.guessDebugger(); + if (guess) { + type = guess.type; + } + } - if (!resolvedConfig) { - // User canceled resolving of interactive variables, silently return - return false; - } + this.initCancellationToken = new CancellationTokenSource(); + const configByProviders = await this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config!, this.initCancellationToken.token); + // a falsy config indicates an aborted launch + if (configByProviders && configByProviders.type) { + try { + const resolvedConfig = await this.substituteVariables(launch, configByProviders); - if (!this.configurationManager.getDebugger(resolvedConfig.type) || (config.request !== 'attach' && config.request !== 'launch')) { - let message: string; - if (config.request !== 'attach' && config.request !== 'launch') { - message = config.request ? nls.localize('debugRequestNotSupported', "Attribute '{0}' has an unsupported value '{1}' in the chosen debug configuration.", 'request', config.request) - : nls.localize('debugRequesMissing', "Attribute '{0}' is missing from the chosen debug configuration.", 'request'); - - } else { - message = resolvedConfig.type ? nls.localize('debugTypeNotSupported', "Configured debug type '{0}' is not supported.", resolvedConfig.type) : - nls.localize('debugTypeMissing', "Missing property 'type' for the chosen launch configuration."); - } - - return this.showError(message).then(() => false); - } - - const workspace = launch ? launch.workspace : undefined; - return this.runTaskAndCheckErrors(workspace, resolvedConfig.preLaunchTask).then(result => { - if (result === TaskRunResult.Success) { - return this.doCreateSession(workspace, { resolved: resolvedConfig, unresolved: unresolvedConfig }, options); - } - return false; - }); - }, err => { - if (err && err.message) { - return this.showError(err.message).then(() => false); - } - if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { - return this.showError(nls.localize('noFolderWorkspaceDebugError', "The active file can not be debugged. Make sure it is saved and that you have a debug extension installed for that file type.")) - .then(() => false); - } - - return !!launch && launch.openConfigFile(false, true, undefined, this.initCancellationToken ? this.initCancellationToken.token : undefined).then(() => false); - }); + if (!resolvedConfig) { + // User canceled resolving of interactive variables, silently return + return false; } - if (launch && type && config === null) { // show launch.json only for "config" being "null". - return launch.openConfigFile(false, true, type, this.initCancellationToken ? this.initCancellationToken.token : undefined).then(() => false); + if (!this.configurationManager.getDebugger(resolvedConfig.type) || (configByProviders.request !== 'attach' && configByProviders.request !== 'launch')) { + let message: string; + if (configByProviders.request !== 'attach' && configByProviders.request !== 'launch') { + message = configByProviders.request ? nls.localize('debugRequestNotSupported', "Attribute '{0}' has an unsupported value '{1}' in the chosen debug configuration.", 'request', configByProviders.request) + : nls.localize('debugRequesMissing', "Attribute '{0}' is missing from the chosen debug configuration.", 'request'); + + } else { + message = resolvedConfig.type ? nls.localize('debugTypeNotSupported', "Configured debug type '{0}' is not supported.", resolvedConfig.type) : + nls.localize('debugTypeMissing', "Missing property 'type' for the chosen launch configuration."); + } + + await this.showError(message); + return false; + } + + const workspace = launch ? launch.workspace : undefined; + const taskResult = await this.runTaskAndCheckErrors(workspace, resolvedConfig.preLaunchTask); + if (taskResult === TaskRunResult.Success) { + return this.doCreateSession(workspace, { resolved: resolvedConfig, unresolved: unresolvedConfig }, options); + } + return false; + } catch (err) { + if (err && err.message) { + await this.showError(err.message); + } else if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { + await this.showError(nls.localize('noFolderWorkspaceDebugError', "The active file can not be debugged. Make sure it is saved and that you have a debug extension installed for that file type.")); + } + if (launch) { + await launch.openConfigFile(false, true, undefined, this.initCancellationToken ? this.initCancellationToken.token : undefined); } return false; - }); - }); + } + } + + if (launch && type && configByProviders === null) { // show launch.json only for "config" being "null". + await launch.openConfigFile(false, true, type, this.initCancellationToken ? this.initCancellationToken.token : undefined); + } + + return false; } /** * instantiates the new session, initializes the session, registers session listeners and reports telemetry */ - private doCreateSession(root: IWorkspaceFolder | undefined, configuration: { resolved: IConfig, unresolved: IConfig | undefined }, options?: IDebugSessionOptions): Promise { + private async doCreateSession(root: IWorkspaceFolder | undefined, configuration: { resolved: IConfig, unresolved: IConfig | undefined }, options?: IDebugSessionOptions): Promise { const session = this.instantiationService.createInstance(DebugSession, configuration, root, this.model, options); this.model.addSession(session); @@ -431,10 +438,11 @@ export class DebugService implements IDebugService { const openDebug = this.configurationService.getValue('debug').openDebug; // Open debug viewlet based on the visibility of the side bar and openDebug setting. Do not open for 'run without debug' if (!configuration.resolved.noDebug && (openDebug === 'openOnSessionStart' || (openDebug === 'openOnFirstSessionStart' && this.viewModel.firstSessionStart))) { - this.viewletService.openViewlet(VIEWLET_ID).then(undefined, errors.onUnexpectedError); + await this.viewletService.openViewlet(VIEWLET_ID); } - return this.launchOrAttachToSession(session).then(() => { + try { + await this.launchOrAttachToSession(session); const internalConsoleOptions = session.configuration.internalConsoleOptions || this.configurationService.getValue('debug').internalConsoleOptions; if (internalConsoleOptions === 'openOnSessionStart' || (this.viewModel.firstSessionStart && internalConsoleOptions === 'openOnFirstSessionStart')) { @@ -452,12 +460,14 @@ export class DebugService implements IDebugService { // 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) => { + await this.telemetryDebugSessionStart(root, session.configuration.type); + + return true; + } catch (error) { if (errors.isPromiseCanceledError(error)) { // don't show 'canceled' error messages to the user #7906 - return Promise.resolve(false); + return false; } // Show the repl if some error got logged there #5870 @@ -467,27 +477,29 @@ export class DebugService implements IDebugService { if (session.configuration && session.configuration.request === 'attach' && session.configuration.__autoAttach) { // ignore attach timeouts in auto attach mode - return Promise.resolve(false); + return false; } const errorMessage = error instanceof Error ? error.message : error; this.telemetryDebugMisconfiguration(session.configuration ? session.configuration.type : undefined, errorMessage); - return this.showError(errorMessage, isErrorWithActions(error) ? error.actions : []).then(() => false); - }); + + await this.showError(errorMessage, isErrorWithActions(error) ? error.actions : []); + return false; + } } - private launchOrAttachToSession(session: IDebugSession, forceFocus = false): Promise { + private async launchOrAttachToSession(session: IDebugSession, forceFocus = false): Promise { const dbgr = this.configurationManager.getDebugger(session.configuration.type); - return session.initialize(dbgr!).then(() => { - return session.launchOrAttach(session.configuration).then(() => { - if (forceFocus || !this.viewModel.focusedSession) { - this.focusStackFrame(undefined, undefined, session); - } - }); - }).then(undefined, err => { + try { + await session.initialize(dbgr!); + await session.launchOrAttach(session.configuration); + if (forceFocus || !this.viewModel.focusedSession) { + await this.focusStackFrame(undefined, undefined, session); + } + } catch (err) { session.shutdown(); return Promise.reject(err); - }); + } } private registerSessionListeners(session: IDebugSession): void { @@ -506,7 +518,7 @@ export class DebugService implements IDebugService { } })); - this.toDispose.push(session.onDidEndAdapter(adapterExitEvent => { + this.toDispose.push(session.onDidEndAdapter(async adapterExitEvent => { if (adapterExitEvent.error) { this.notificationService.error(nls.localize('debugAdapterCrash', "Debug adapter process has terminated unexpectedly ({0})", adapterExitEvent.error.message || adapterExitEvent.error.toString())); @@ -520,9 +532,11 @@ export class DebugService implements IDebugService { this.telemetryDebugSessionStop(session, adapterExitEvent); if (session.configuration.postDebugTask) { - this.runTask(session.root, session.configuration.postDebugTask).then(undefined, err => - this.notificationService.error(err) - ); + try { + await this.runTask(session.root, session.configuration.postDebugTask); + } catch (err) { + this.notificationService.error(err); + } } session.shutdown(); this.endInitializingState(); @@ -530,7 +544,7 @@ export class DebugService implements IDebugService { const focusedSession = this.viewModel.focusedSession; if (focusedSession && focusedSession.getId() === session.getId()) { - this.focusStackFrame(undefined); + await this.focusStackFrame(undefined); } if (this.model.getSessions().length === 0) { @@ -548,87 +562,93 @@ export class DebugService implements IDebugService { })); } - restartSession(session: IDebugSession, restartData?: any): Promise { - return this.textFileService.saveAll().then(() => { - const isAutoRestart = !!restartData; - const runTasks: () => Promise = () => { - if (isAutoRestart) { - // Do not run preLaunch and postDebug tasks for automatic restarts - return Promise.resolve(TaskRunResult.Success); + async restartSession(session: IDebugSession, restartData?: any): Promise { + await this.textFileService.saveAll(); + const isAutoRestart = !!restartData; + + const runTasks: () => Promise = async () => { + if (isAutoRestart) { + // Do not run preLaunch and postDebug tasks for automatic restarts + return Promise.resolve(TaskRunResult.Success); + } + + await this.runTask(session.root, session.configuration.postDebugTask); + return this.runTaskAndCheckErrors(session.root, session.configuration.preLaunchTask); + }; + + if (session.capabilities.supportsRestartRequest) { + const taskResult = await runTasks(); + if (taskResult === TaskRunResult.Success) { + await session.restart(); + } + + return; + } + + if (isExtensionHostDebugging(session.configuration)) { + const taskResult = await runTasks(); + if (taskResult === TaskRunResult.Success) { + this.extensionHostDebugService.reload(session.getId()); + } + + return; + } + + const shouldFocus = !!this.viewModel.focusedSession && session.getId() === this.viewModel.focusedSession.getId(); + // If the restart is automatic -> disconnect, otherwise -> terminate #55064 + if (isAutoRestart) { + await session.disconnect(true); + } else { + await session.terminate(true); + } + + return new Promise((c, e) => { + setTimeout(async () => { + const taskResult = await runTasks(); + if (taskResult !== TaskRunResult.Success) { + return; } - return this.runTask(session.root, session.configuration.postDebugTask) - .then(() => this.runTaskAndCheckErrors(session.root, session.configuration.preLaunchTask)); - }; + // Read the configuration again if a launch.json has been changed, if not just use the inmemory configuration + let needsToSubstitute = false; + let unresolved: IConfig | undefined; + const launch = session.root ? this.configurationManager.getLaunch(session.root.uri) : undefined; + if (launch) { + unresolved = launch.getConfiguration(session.configuration.name); + if (unresolved && !equals(unresolved, session.unresolvedConfiguration)) { + // Take the type from the session since the debug extension might overwrite it #21316 + unresolved.type = session.configuration.type; + unresolved.noDebug = session.configuration.noDebug; + needsToSubstitute = true; + } + } - if (session.capabilities.supportsRestartRequest) { - return runTasks().then(taskResult => taskResult === TaskRunResult.Success ? session.restart() : undefined); - } + let resolved: IConfig | undefined | null = session.configuration; + if (launch && needsToSubstitute && unresolved) { + this.initCancellationToken = new CancellationTokenSource(); + const resolvedByProviders = await this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved.type, unresolved, this.initCancellationToken.token); + if (resolvedByProviders) { + resolved = await this.substituteVariables(launch, resolvedByProviders); + } else { + resolved = resolvedByProviders; + } + } - if (isExtensionHostDebugging(session.configuration)) { - return runTasks().then(taskResult => taskResult === TaskRunResult.Success ? this.extensionHostDebugService.reload(session.getId()) : undefined); - } + if (!resolved) { + return c(undefined); + } - const shouldFocus = !!this.viewModel.focusedSession && session.getId() === this.viewModel.focusedSession.getId(); - // If the restart is automatic -> disconnect, otherwise -> terminate #55064 - return (isAutoRestart ? session.disconnect(true) : session.terminate(true)).then(() => { + session.setConfiguration({ resolved, unresolved }); + session.configuration.__restart = restartData; - return new Promise((c, e) => { - setTimeout(() => { - runTasks().then(taskResult => { - if (taskResult !== TaskRunResult.Success) { - return; - } - - // Read the configuration again if a launch.json has been changed, if not just use the inmemory configuration - let needsToSubstitute = false; - let unresolved: IConfig | undefined; - const launch = session.root ? this.configurationManager.getLaunch(session.root.uri) : undefined; - if (launch) { - unresolved = launch.getConfiguration(session.configuration.name); - if (unresolved && !equals(unresolved, session.unresolvedConfiguration)) { - // Take the type from the session since the debug extension might overwrite it #21316 - unresolved.type = session.configuration.type; - unresolved.noDebug = session.configuration.noDebug; - needsToSubstitute = true; - } - } - - let substitutionThenable: Promise = Promise.resolve(session.configuration); - if (launch && needsToSubstitute && unresolved) { - this.initCancellationToken = new CancellationTokenSource(); - substitutionThenable = this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved.type, unresolved, this.initCancellationToken.token) - .then(resolved => { - if (resolved) { - // start debugging - return this.substituteVariables(launch, resolved); - } else if (resolved === null) { - // abort debugging silently and open launch.json - return Promise.resolve(null); - } else { - // abort debugging silently - return Promise.resolve(undefined); - } - }); - } - substitutionThenable.then(resolved => { - - if (!resolved) { - return c(undefined); - } - - session.setConfiguration({ resolved, unresolved }); - session.configuration.__restart = restartData; - - this.launchOrAttachToSession(session, shouldFocus).then(() => { - this._onDidNewSession.fire(session); - c(undefined); - }, err => e(err)); - }); - }); - }, 300); - }); - }); + try { + await this.launchOrAttachToSession(session, shouldFocus); + this._onDidNewSession.fire(session); + c(undefined); + } catch (error) { + e(error); + } + }, 300); }); } @@ -646,7 +666,7 @@ export class DebugService implements IDebugService { return Promise.all(sessions.map(s => s.terminate())); } - private substituteVariables(launch: ILaunch | undefined, config: IConfig): Promise { + private async substituteVariables(launch: ILaunch | undefined, config: IConfig): Promise { const dbg = this.configurationManager.getDebugger(config.type); if (dbg) { let folder: IWorkspaceFolder | undefined = undefined; @@ -658,12 +678,12 @@ export class DebugService implements IDebugService { folder = folders[0]; } } - return dbg.substituteVariables(folder, config).then(config => { - return config; - }, (err: Error) => { + try { + return await dbg.substituteVariables(folder, config); + } catch (err) { this.showError(err.message); return undefined; // bail out - }); + } } return Promise.resolve(config); } @@ -681,13 +701,13 @@ export class DebugService implements IDebugService { //---- task management - private runTaskAndCheckErrors(root: IWorkspaceFolder | undefined, taskId: string | TaskIdentifier | undefined): Promise { - - return this.runTask(root, taskId).then((taskSummary: ITaskSummary) => { + private async runTaskAndCheckErrors(root: IWorkspaceFolder | undefined, taskId: string | TaskIdentifier | undefined): Promise { + try { + const taskSummary = await this.runTask(root, taskId); const errorCount = taskId ? this.markerService.getStatistics().errors : 0; const successExitCode = taskSummary && taskSummary.exitCode === 0; - const failureExitCode = taskSummary && taskSummary.exitCode !== undefined && taskSummary.exitCode !== 0; + const failureExitCode = taskSummary && taskSummary.exitCode !== 0; const onTaskErrors = this.configurationService.getValue('debug').onTaskErrors; if (successExitCode || onTaskErrors === 'debugAnyway' || (errorCount === 0 && !failureExitCode)) { return TaskRunResult.Success; @@ -702,34 +722,35 @@ export class DebugService implements IDebugService { ? nls.localize('preLaunchTaskErrors', "Errors exist after running preLaunchTask '{0}'.", taskLabel) : errorCount === 1 ? nls.localize('preLaunchTaskError', "Error exists after running preLaunchTask '{0}'.", taskLabel) - : nls.localize('preLaunchTaskExitCode', "The preLaunchTask '{0}' terminated with exit code {1}.", taskLabel, taskSummary.exitCode); + : nls.localize('preLaunchTaskExitCode', "The preLaunchTask '{0}' terminated with exit code {1}.", taskLabel, taskSummary ? taskSummary.exitCode : 0); - return this.dialogService.show(severity.Warning, message, [nls.localize('debugAnyway', "Debug Anyway"), nls.localize('showErrors', "Show Errors"), nls.localize('cancel', "Cancel")], { + const result = await this.dialogService.show(severity.Warning, message, [nls.localize('debugAnyway', "Debug Anyway"), nls.localize('showErrors', "Show Errors"), nls.localize('cancel', "Cancel")], { checkbox: { label: nls.localize('remember', "Remember my choice in user settings"), }, cancelId: 2 - }).then(result => { - if (result.choice === 2) { - return Promise.resolve(TaskRunResult.Failure); - } - const debugAnyway = result.choice === 0; - if (result.checkboxChecked) { - this.configurationService.updateValue('debug.onTaskErrors', debugAnyway ? 'debugAnyway' : 'showErrors'); - } - if (debugAnyway) { - return TaskRunResult.Success; - } - - this.panelService.openPanel(Constants.MARKERS_PANEL_ID); - return Promise.resolve(TaskRunResult.Failure); }); - }, (err: TaskError) => { - return this.showError(err.message, [this.taskService.configureAction()]); - }); + + if (result.choice === 2) { + return Promise.resolve(TaskRunResult.Failure); + } + const debugAnyway = result.choice === 0; + if (result.checkboxChecked) { + this.configurationService.updateValue('debug.onTaskErrors', debugAnyway ? 'debugAnyway' : 'showErrors'); + } + if (debugAnyway) { + return TaskRunResult.Success; + } + + this.panelService.openPanel(Constants.MARKERS_PANEL_ID); + return Promise.resolve(TaskRunResult.Failure); + } catch (err) { + await this.showError(err.message, [this.taskService.configureAction()]); + return TaskRunResult.Failure; + } } - private runTask(root: IWorkspaceFolder | undefined, taskId: string | TaskIdentifier | undefined): Promise { + private async runTask(root: IWorkspace | IWorkspaceFolder | undefined, taskId: string | TaskIdentifier | undefined): Promise { if (!taskId) { return Promise.resolve(null); } @@ -737,58 +758,72 @@ export class DebugService implements IDebugService { return Promise.reject(new Error(nls.localize('invalidTaskReference', "Task '{0}' can not be referenced from a launch configuration that is in a different workspace folder.", typeof taskId === 'string' ? taskId : taskId.type))); } // run a task before starting a debug session - return this.taskService.getTask(root, taskId).then(task => { - if (!task) { - const errorMessage = typeof taskId === 'string' - ? nls.localize('DebugTaskNotFoundWithTaskId', "Could not find the task '{0}'.", taskId) - : nls.localize('DebugTaskNotFound', "Could not find the specified task."); - return Promise.reject(createErrorWithActions(errorMessage)); + const task = await this.taskService.getTask(root, taskId); + if (!task) { + const errorMessage = typeof taskId === 'string' + ? nls.localize('DebugTaskNotFoundWithTaskId', "Could not find the task '{0}'.", taskId) + : nls.localize('DebugTaskNotFound', "Could not find the specified task."); + return Promise.reject(createErrorWithActions(errorMessage)); + } + + // If a task is missing the problem matcher the promise will never complete, so we need to have a workaround #35340 + let taskStarted = false; + const inactivePromise: Promise = new Promise((c, e) => once(e => { + // When a task isBackground it will go inactive when it is safe to launch. + // But when a background task is terminated by the user, it will also fire an inactive event. + // This means that we will not get to see the real exit code from running the task (undefined when terminated by the user). + // Catch the ProcessEnded event here, which occurs before inactive, and capture the exit code to prevent this. + return (e.kind === TaskEventKind.Inactive + || (e.kind === TaskEventKind.ProcessEnded && e.exitCode === undefined)) + && e.taskId === task._id; + }, this.taskService.onDidStateChange)(e => { + taskStarted = true; + c(e.kind === TaskEventKind.ProcessEnded ? { exitCode: e.exitCode } : null); + })); + + const promise: Promise = this.taskService.getActiveTasks().then(async (tasks): Promise => { + if (tasks.filter(t => t._id === task._id).length) { + // Check that the task isn't busy and if it is, wait for it + const busyTasks = await this.taskService.getBusyTasks(); + if (busyTasks.filter(t => t._id === task._id).length) { + return inactivePromise; + } + // task is already running and isn't busy - nothing to do. + return Promise.resolve(null); + } + once(e => ((e.kind === TaskEventKind.Active) || (e.kind === TaskEventKind.DependsOnStarted)) && e.taskId === task._id, this.taskService.onDidStateChange)(() => { + // Task is active, so everything seems to be fine, no need to prompt after 10 seconds + // Use case being a slow running task should not be prompted even though it takes more than 10 seconds + taskStarted = true; + }); + const taskPromise = this.taskService.run(task); + if (task.configurationProperties.isBackground) { + return inactivePromise; } - // If a task is missing the problem matcher the promise will never complete, so we need to have a workaround #35340 - let taskStarted = false; - const promise: Promise = this.taskService.getActiveTasks().then(tasks => { - if (tasks.filter(t => t._id === task._id).length) { - // task is already running - nothing to do. - return Promise.resolve(null); + return taskPromise; + }); + + return new Promise((c, e) => { + promise.then(result => { + taskStarted = true; + c(result); + }, error => e(error)); + + setTimeout(() => { + if (!taskStarted) { + const errorMessage = typeof taskId === 'string' + ? nls.localize('taskNotTrackedWithTaskId', "The specified task cannot be tracked.") + : nls.localize('taskNotTracked', "The task '{0}' cannot be tracked.", JSON.stringify(taskId)); + e({ severity: severity.Error, message: errorMessage }); } - once(e => ((e.kind === TaskEventKind.Active) || (e.kind === TaskEventKind.DependsOnStarted)) && e.taskId === task._id, this.taskService.onDidStateChange)(() => { - // Task is active, so everything seems to be fine, no need to prompt after 10 seconds - // Use case being a slow running task should not be prompted even though it takes more than 10 seconds - taskStarted = true; - }); - const taskPromise = this.taskService.run(task); - if (task.configurationProperties.isBackground) { - return new Promise((c, e) => once(e => e.kind === TaskEventKind.Inactive && e.taskId === task._id, this.taskService.onDidStateChange)(() => { - taskStarted = true; - c(null); - })); - } - - return taskPromise; - }); - - return new Promise((c, e) => { - promise.then(result => { - taskStarted = true; - c(result); - }, error => e(error)); - - setTimeout(() => { - if (!taskStarted) { - const errorMessage = typeof taskId === 'string' - ? nls.localize('taskNotTrackedWithTaskId', "The specified task cannot be tracked.") - : nls.localize('taskNotTracked', "The task '{0}' cannot be tracked.", JSON.stringify(taskId)); - e({ severity: severity.Error, message: errorMessage }); - } - }, 10000); - }); + }, 10000); }); } //---- focus management - focusStackFrame(stackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession, explicit?: boolean): void { + async focusStackFrame(stackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession, explicit?: boolean): Promise { if (!session) { if (stackFrame || thread) { session = stackFrame ? stackFrame.thread.session : thread!.session; @@ -817,18 +852,17 @@ export class DebugService implements IDebugService { } if (stackFrame) { - stackFrame.openInEditor(this.editorService, true).then(editor => { - if (editor) { - const control = editor.getControl(); - if (stackFrame && isCodeEditor(control) && control.hasModel()) { - const model = control.getModel(); - if (stackFrame.range.startLineNumber <= model.getLineCount()) { - 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)); - } + const editor = await stackFrame.openInEditor(this.editorService, true); + if (editor) { + const control = editor.getControl(); + if (stackFrame && isCodeEditor(control) && control.hasModel()) { + const model = control.getModel(); + if (stackFrame.range.startLineNumber <= model.getLineCount()) { + 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); @@ -949,12 +983,12 @@ export class DebugService implements IDebugService { this.storeBreakpoints(); } - sendAllBreakpoints(session?: IDebugSession): Promise { - return Promise.all(distinct(this.model.getBreakpoints(), bp => bp.uri.toString()).map(bp => this.sendBreakpoints(bp.uri, false, session))) - .then(() => this.sendFunctionBreakpoints(session)) - // send exception breakpoints at the end since some debug adapters rely on the order - .then(() => this.sendExceptionBreakpoints(session)) - .then(() => this.sendDataBreakpoints(session)); + async sendAllBreakpoints(session?: IDebugSession): Promise { + await Promise.all(distinct(this.model.getBreakpoints(), bp => bp.uri.toString()).map(bp => this.sendBreakpoints(bp.uri, false, session))); + await this.sendFunctionBreakpoints(session); + await this.sendDataBreakpoints(session); + // send exception breakpoints at the end since some debug adapters rely on the order + await this.sendExceptionBreakpoints(session); } private sendBreakpoints(modelUri: uri, sourceModified = false, session?: IDebugSession): Promise { @@ -989,11 +1023,12 @@ export class DebugService implements IDebugService { }); } - private sendToOneOrAllSessions(session: IDebugSession | undefined, send: (session: IDebugSession) => Promise): Promise { + private async sendToOneOrAllSessions(session: IDebugSession | undefined, send: (session: IDebugSession) => Promise): Promise { if (session) { - return send(session); + await send(session); + } else { + await Promise.all(this.model.getSessions().map(s => send(s))); } - return Promise.all(this.model.getSessions().map(s => send(s))).then(() => undefined); } private onFileChanges(fileChangesEvent: FileChangesEvent): void { diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index f4bc1c74e3..9a9e68b396 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -30,8 +30,6 @@ import { Range } from 'vs/editor/common/core/range'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ReplModel } from 'vs/workbench/contrib/debug/common/replModel'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { INotificationService } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { variableSetEmitter } from 'vs/workbench/contrib/debug/browser/variablesView'; import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; @@ -65,7 +63,7 @@ export class DebugSession implements IDebugSession { constructor( private _configuration: { resolved: IConfig, unresolved: IConfig | undefined }, - public root: IWorkspaceFolder, + public root: IWorkspaceFolder | undefined, private model: DebugModel, options: IDebugSessionOptions | undefined, @IDebugService private readonly debugService: IDebugService, @@ -74,7 +72,6 @@ export class DebugSession implements IDebugSession { @IConfigurationService private readonly configurationService: IConfigurationService, @IViewletService private readonly viewletService: IViewletService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @INotificationService private readonly notificationService: INotificationService, @IProductService private readonly productService: IProductService, @IExtensionHostDebugService private readonly extensionHostDebugService: IExtensionHostDebugService, @IOpenerService private readonly openerService: IOpenerService @@ -183,110 +180,100 @@ export class DebugSession implements IDebugSession { /** * create and initialize a new debug adapter for this session */ - initialize(dbgr: IDebugger): Promise { + async initialize(dbgr: IDebugger): Promise { if (this.raw) { // if there was already a connection make sure to remove old listeners this.shutdown(); } - return dbgr.getCustomTelemetryService().then(customTelemetryService => { + try { + const customTelemetryService = await dbgr.getCustomTelemetryService(); + const debugAdapter = await dbgr.createDebugAdapter(this); + this.raw = new RawDebugSession(debugAdapter, dbgr, this.telemetryService, customTelemetryService, this.extensionHostDebugService, this.openerService); - return dbgr.createDebugAdapter(this).then(debugAdapter => { - - this.raw = new RawDebugSession(debugAdapter, dbgr, this.telemetryService, customTelemetryService, this.extensionHostDebugService, this.openerService); - - return this.raw.start().then(() => { - - this.registerListeners(); - - return this.raw!.initialize({ - clientID: 'vscode', - clientName: this.productService.nameLong, - adapterID: this.configuration.type, - pathFormat: 'path', - linesStartAt1: true, - columnsStartAt1: true, - supportsVariableType: true, // #8858 - supportsVariablePaging: true, // #9537 - supportsRunInTerminalRequest: true, // #10574 - locale: platform.locale - }).then(() => { - this.initialized = true; - this._onDidChangeState.fire(); - this.model.setExceptionBreakpoints(this.raw!.capabilities.exceptionBreakpointFilters || []); - }); - }); + await this.raw.start(); + this.registerListeners(); + await this.raw!.initialize({ + clientID: 'vscode', + clientName: this.productService.nameLong, + adapterID: this.configuration.type, + pathFormat: 'path', + linesStartAt1: true, + columnsStartAt1: true, + supportsVariableType: true, // #8858 + supportsVariablePaging: true, // #9537 + supportsRunInTerminalRequest: true, // #10574 + locale: platform.locale }); - }).then(undefined, err => { + this.initialized = true; this._onDidChangeState.fire(); - return Promise.reject(err); - }); + this.model.setExceptionBreakpoints(this.raw!.capabilities.exceptionBreakpointFilters || []); + } catch (err) { + this.initialized = true; + this._onDidChangeState.fire(); + throw err; + } } /** * launch or attach to the debuggee */ - launchOrAttach(config: IConfig): Promise { - if (this.raw) { - - // __sessionID only used for EH debugging (but we add it always for now...) - config.__sessionId = this.getId(); - - return this.raw.launchOrAttach(config).then(result => { - return undefined; - }); + async launchOrAttach(config: IConfig): Promise { + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + + // __sessionID only used for EH debugging (but we add it always for now...) + config.__sessionId = this.getId(); + await this.raw.launchOrAttach(config); + } /** * end the current debug adapter session */ - terminate(restart = false): Promise { - if (this.raw) { - this.cancelAllRequests(); - if (this.raw.capabilities.supportsTerminateRequest && this._configuration.resolved.request === 'launch') { - return this.raw.terminate(restart).then(response => { - return undefined; - }); - } - return this.raw.disconnect(restart).then(response => { - return undefined; - }); + async terminate(restart = false): Promise { + if (!this.raw) { + throw new Error('no debug adapter'); + } + + this.cancelAllRequests(); + if (this.raw.capabilities.supportsTerminateRequest && this._configuration.resolved.request === 'launch') { + await this.raw.terminate(restart); + } else { + await this.raw.disconnect(restart); } - return Promise.reject(new Error('no debug adapter')); } /** * end the current debug adapter session */ - disconnect(restart = false): Promise { - if (this.raw) { - this.cancelAllRequests(); - return this.raw.disconnect(restart).then(response => { - return undefined; - }); + async disconnect(restart = false): Promise { + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + + this.cancelAllRequests(); + await this.raw.disconnect(restart); } /** * restart debug adapter session */ - restart(): Promise { - if (this.raw) { - this.cancelAllRequests(); - return this.raw.restart().then(() => undefined); + async restart(): Promise { + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + + this.cancelAllRequests(); + await this.raw.restart(); } - sendBreakpoints(modelUri: URI, breakpointsToSend: IBreakpoint[], sourceModified: boolean): Promise { - + async sendBreakpoints(modelUri: URI, breakpointsToSend: IBreakpoint[], sourceModified: boolean): Promise { if (!this.raw) { - return Promise.reject(new Error('no debug adapter')); + throw new Error('no debug adapter'); } if (!this.raw.readyForBreakpoints) { @@ -302,233 +289,252 @@ export class DebugSession implements IDebugSession { rawSource.path = normalizeDriveLetter(rawSource.path); } - return this.raw.setBreakpoints({ + const response = await this.raw.setBreakpoints({ source: rawSource, 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 = new Map(); + for (let i = 0; i < breakpointsToSend.length; i++) { + data.set(breakpointsToSend[i].getId(), response.body.breakpoints[i]); + } + + this.model.setBreakpointSessionData(this.getId(), this.capabilities, data); + } + } + + async sendFunctionBreakpoints(fbpts: IFunctionBreakpoint[]): Promise { + if (!this.raw) { + throw new Error('no debug adapter'); + } + + if (this.raw.readyForBreakpoints) { + const response = await this.raw.setFunctionBreakpoints({ breakpoints: fbpts }); if (response && response.body) { const data = new Map(); - for (let i = 0; i < breakpointsToSend.length; i++) { - data.set(breakpointsToSend[i].getId(), response.body.breakpoints[i]); + for (let i = 0; i < fbpts.length; i++) { + data.set(fbpts[i].getId(), response.body.breakpoints[i]); } - this.model.setBreakpointSessionData(this.getId(), this.capabilities, data); } - }); + } } - sendFunctionBreakpoints(fbpts: IFunctionBreakpoint[]): Promise { - if (this.raw) { - if (this.raw.readyForBreakpoints) { - return this.raw.setFunctionBreakpoints({ breakpoints: fbpts }).then(response => { - if (response && response.body) { - const data = new Map(); - for (let i = 0; i < fbpts.length; i++) { - data.set(fbpts[i].getId(), response.body.breakpoints[i]); - } - this.model.setBreakpointSessionData(this.getId(), this.capabilities, data); - } - }); - } - - return Promise.resolve(undefined); + async sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise { + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + if (this.raw.readyForBreakpoints) { + await this.raw.setExceptionBreakpoints({ filters: exbpts.map(exb => exb.filter) }); + } } - sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise { - if (this.raw) { - if (this.raw.readyForBreakpoints) { - return this.raw.setExceptionBreakpoints({ filters: exbpts.map(exb => exb.filter) }).then(() => undefined); - } - return Promise.resolve(undefined); + async dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null, description: string, canPersist?: boolean }> { + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + if (!this.raw.readyForBreakpoints) { + throw new Error(nls.localize('sessionNotReadyForBreakpoints', "Session is not ready for breakpoints")); + } + + const response = await this.raw.dataBreakpointInfo({ name, variablesReference }); + return response.body; } - dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null, description: string, canPersist?: boolean }> { - if (this.raw) { - if (this.raw.readyForBreakpoints) { - return this.raw.dataBreakpointInfo({ name, variablesReference }).then(response => response.body); - } - return Promise.reject(new Error(nls.localize('sessionNotReadyForBreakpoints', "Session is not ready for breakpoints"))); + async sendDataBreakpoints(dataBreakpoints: IDataBreakpoint[]): Promise { + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); - } - sendDataBreakpoints(dataBreakpoints: IDataBreakpoint[]): Promise { - if (this.raw) { - if (this.raw.readyForBreakpoints) { - return this.raw.setDataBreakpoints({ breakpoints: dataBreakpoints }).then(response => { - if (response && response.body) { - const data = new Map(); - for (let i = 0; i < dataBreakpoints.length; i++) { - data.set(dataBreakpoints[i].getId(), response.body.breakpoints[i]); - } - this.model.setBreakpointSessionData(this.getId(), this.capabilities, data); - } - }); + if (this.raw.readyForBreakpoints) { + const response = await this.raw.setDataBreakpoints({ breakpoints: dataBreakpoints }); + if (response && response.body) { + const data = new Map(); + for (let i = 0; i < dataBreakpoints.length; i++) { + data.set(dataBreakpoints[i].getId(), response.body.breakpoints[i]); + } + this.model.setBreakpointSessionData(this.getId(), this.capabilities, data); } - return Promise.resolve(undefined); } - return Promise.reject(new Error('no debug adapter')); } async breakpointsLocations(uri: URI, lineNumber: number): Promise { - if (this.raw) { - const source = this.getRawSource(uri); - const response = await this.raw.breakpointLocations({ source, line: lineNumber }); - const positions = response.body.breakpoints.map(bp => ({ lineNumber: bp.line, column: bp.column || 1 })); - - return distinct(positions, p => `${p.lineNumber}:${p.column}`); + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + + const source = this.getRawSource(uri); + const response = await this.raw.breakpointLocations({ source, line: lineNumber }); + if (!response.body || !response.body.breakpoints) { + return []; + } + + const positions = response.body.breakpoints.map(bp => ({ lineNumber: bp.line, column: bp.column || 1 })); + + return distinct(positions, p => `${p.lineNumber}:${p.column}`); } customRequest(request: string, args: any): Promise { - if (this.raw) { - return this.raw.custom(request, args); + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + + return this.raw.custom(request, args); } stackTrace(threadId: number, startFrame: number, levels: number): Promise { - if (this.raw) { - const token = this.getNewCancellationToken(threadId); - return this.raw.stackTrace({ threadId, startFrame, levels }, token); + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + + const token = this.getNewCancellationToken(threadId); + return this.raw.stackTrace({ threadId, startFrame, levels }, token); } - exceptionInfo(threadId: number): Promise { - if (this.raw) { - return this.raw.exceptionInfo({ threadId }).then(response => { - if (response) { - return { - id: response.body.exceptionId, - description: response.body.description, - breakMode: response.body.breakMode, - details: response.body.details - }; - } - return undefined; - }); + async exceptionInfo(threadId: number): Promise { + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + + const response = await this.raw.exceptionInfo({ threadId }); + if (response) { + return { + id: response.body.exceptionId, + description: response.body.description, + breakMode: response.body.breakMode, + details: response.body.details + }; + } + + return undefined; } scopes(frameId: number, threadId: number): Promise { - if (this.raw) { - const token = this.getNewCancellationToken(threadId); - return this.raw.scopes({ frameId }, token); + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + + const token = this.getNewCancellationToken(threadId); + return this.raw.scopes({ frameId }, token); } variables(variablesReference: number, threadId: number | undefined, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise { - if (this.raw) { - const token = threadId ? this.getNewCancellationToken(threadId) : undefined; - return this.raw.variables({ variablesReference, filter, start, count }, token); + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + + const token = threadId ? this.getNewCancellationToken(threadId) : undefined; + return this.raw.variables({ variablesReference, filter, start, count }, token); } evaluate(expression: string, frameId: number, context?: string): Promise { - if (this.raw) { - return this.raw.evaluate({ expression, frameId, context }); + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + + return this.raw.evaluate({ expression, frameId, context }); } - restartFrame(frameId: number, threadId: number): Promise { - if (this.raw) { - return this.raw.restartFrame({ frameId }, threadId).then(() => undefined); + async restartFrame(frameId: number, threadId: number): Promise { + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + + await this.raw.restartFrame({ frameId }, threadId); } - next(threadId: number): Promise { - if (this.raw) { - return this.raw.next({ threadId }).then(() => undefined); + async next(threadId: number): Promise { + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + + await this.raw.next({ threadId }); } - stepIn(threadId: number): Promise { - if (this.raw) { - return this.raw.stepIn({ threadId }).then(() => undefined); + async stepIn(threadId: number): Promise { + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + + await this.raw.stepIn({ threadId }); } - stepOut(threadId: number): Promise { - if (this.raw) { - return this.raw.stepOut({ threadId }).then(() => undefined); + async stepOut(threadId: number): Promise { + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + + await this.raw.stepOut({ threadId }); } - stepBack(threadId: number): Promise { - if (this.raw) { - return this.raw.stepBack({ threadId }).then(() => undefined); + async stepBack(threadId: number): Promise { + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + + await this.raw.stepBack({ threadId }); } - continue(threadId: number): Promise { - if (this.raw) { - return this.raw.continue({ threadId }).then(() => undefined); + async continue(threadId: number): Promise { + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + + await this.raw.continue({ threadId }); } - reverseContinue(threadId: number): Promise { - if (this.raw) { - return this.raw.reverseContinue({ threadId }).then(() => undefined); + async reverseContinue(threadId: number): Promise { + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + + await this.raw.reverseContinue({ threadId }); } - pause(threadId: number): Promise { - if (this.raw) { - return this.raw.pause({ threadId }).then(() => undefined); + async pause(threadId: number): Promise { + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + + await this.raw.pause({ threadId }); } - terminateThreads(threadIds?: number[]): Promise { - if (this.raw) { - return this.raw.terminateThreads({ threadIds }).then(() => undefined); + async terminateThreads(threadIds?: number[]): Promise { + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + + await this.raw.terminateThreads({ threadIds }); } setVariable(variablesReference: number, name: string, value: string): Promise { - if (this.raw) { - return this.raw.setVariable({ variablesReference, name, value }); + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + + return this.raw.setVariable({ variablesReference, name, value }); } gotoTargets(source: DebugProtocol.Source, line: number, column?: number): Promise { - if (this.raw) { - return this.raw.gotoTargets({ source, line, column }); + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + + return this.raw.gotoTargets({ source, line, column }); } goto(threadId: number, targetId: number): Promise { - if (this.raw) { - return this.raw.goto({ threadId, targetId }); + if (!this.raw) { + throw new Error('no debug adapter'); } - return Promise.reject(new Error('no debug adapter')); + + return this.raw.goto({ threadId, targetId }); } loadSource(resource: URI): Promise { - if (!this.raw) { return Promise.reject(new Error('no debug adapter')); } @@ -546,50 +552,48 @@ export class DebugSession implements IDebugSession { return this.raw.source({ sourceReference: rawSource.sourceReference || 0, source: rawSource }); } - getLoadedSources(): Promise { - if (this.raw) { - return this.raw.loadedSources({}).then(response => { - if (response.body && response.body.sources) { - return response.body.sources.map(src => this.getSource(src)); - } else { - return []; - } - }, () => { - return []; - }); + async getLoadedSources(): Promise { + if (!this.raw) { + return Promise.reject(new Error('no debug adapter')); + } + + const response = await this.raw.loadedSources({}); + if (response.body && response.body.sources) { + return response.body.sources.map(src => this.getSource(src)); + } else { + return []; } - return Promise.reject(new Error('no debug adapter')); } - completions(frameId: number | undefined, text: string, position: Position, overwriteBefore: number, token: CancellationToken): Promise { - if (this.raw) { - return this.raw.completions({ - frameId, - text, - column: position.column, - line: position.lineNumber, - }, token).then(response => { + async completions(frameId: number | undefined, text: string, position: Position, overwriteBefore: number, token: CancellationToken): Promise { + if (!this.raw) { + return Promise.reject(new Error('no debug adapter')); + } - const result: CompletionItem[] = []; - if (response && response.body && response.body.targets) { - response.body.targets.forEach(item => { - if (item && item.label) { - result.push({ - label: item.label, - insertText: item.text || item.label, - kind: completionKindFromString(item.type || 'property'), - filterText: (item.start && item.length) ? text.substr(item.start, item.length).concat(item.label) : undefined, - range: Range.fromPositions(position.delta(0, -(item.length || overwriteBefore)), position), - sortText: item.sortText - }); - } + const response = await this.raw.completions({ + frameId, + text, + column: position.column, + line: position.lineNumber, + }, token); + + const result: CompletionItem[] = []; + if (response && response.body && response.body.targets) { + response.body.targets.forEach(item => { + if (item && item.label) { + result.push({ + label: item.label, + insertText: item.text || item.label, + kind: completionKindFromString(item.type || 'property'), + filterText: (item.start && item.length) ? text.substr(item.start, item.length).concat(item.label) : undefined, + range: Range.fromPositions(position.delta(0, -(item.length || overwriteBefore)), position), + sortText: item.sortText }); } - - return result; }); } - return Promise.reject(new Error('no debug adapter')); + + return result; } //---- threads @@ -674,8 +678,9 @@ export class DebugSession implements IDebugSession { } } - private fetchThreads(stoppedDetails?: IRawStoppedDetails): Promise { - return this.raw ? this.raw.threads().then(response => { + private async fetchThreads(stoppedDetails?: IRawStoppedDetails): Promise { + if (this.raw) { + const response = await this.raw.threads(); if (response && response.body && response.body.threads) { this.model.rawUpdate({ sessionId: this.getId(), @@ -683,7 +688,7 @@ export class DebugSession implements IDebugSession { stoppedDetails }); } - }) : Promise.resolve(undefined); + } } //---- private @@ -693,60 +698,63 @@ export class DebugSession implements IDebugSession { return; } - this.rawListeners.push(this.raw.onDidInitialize(() => { + this.rawListeners.push(this.raw.onDidInitialize(async () => { aria.status(nls.localize('debuggingStarted', "Debugging started.")); - const sendConfigurationDone = () => { + const sendConfigurationDone = async () => { if (this.raw && this.raw.capabilities.supportsConfigurationDoneRequest) { - return this.raw.configurationDone().then(undefined, e => { + try { + await this.raw.configurationDone(); + } catch (e) { // Disconnect the debug session on configuration done error #10596 if (this.raw) { this.raw.disconnect(); } - if (e.command !== 'canceled' && e.message !== 'canceled') { - this.notificationService.error(e); - } - }); + } } return undefined; }; // Send all breakpoints - this.debugService.sendAllBreakpoints(this).then(sendConfigurationDone, sendConfigurationDone) - .then(() => this.fetchThreads()); + try { + await this.debugService.sendAllBreakpoints(this); + } finally { + await sendConfigurationDone(); + } + await this.fetchThreads(); })); - this.rawListeners.push(this.raw.onDidStop(event => { - this.fetchThreads(event.body).then(() => { - const thread = typeof event.body.threadId === 'number' ? this.getThread(event.body.threadId) : undefined; - if (thread) { - // Call fetch call stack twice, the first only return the top stack frame. - // Second retrieves the rest of the call stack. For performance reasons #25605 - const promises = this.model.fetchCallStack(thread); - const focus = () => { - if (!event.body.preserveFocusHint && thread.getCallStack().length) { - this.debugService.focusStackFrame(undefined, thread); - if (thread.stoppedDetails) { - if (this.configurationService.getValue('debug').openDebug === 'openOnDebugBreak') { - this.viewletService.openViewlet(VIEWLET_ID); - } + this.rawListeners.push(this.raw.onDidStop(async event => { + await this.fetchThreads(event.body); + const thread = typeof event.body.threadId === 'number' ? this.getThread(event.body.threadId) : undefined; + if (thread) { + // Call fetch call stack twice, the first only return the top stack frame. + // Second retrieves the rest of the call stack. For performance reasons #25605 + const promises = this.model.fetchCallStack(thread); + const focus = async () => { + if (!event.body.preserveFocusHint && thread.getCallStack().length) { + await this.debugService.focusStackFrame(undefined, thread); + if (thread.stoppedDetails) { + if (this.configurationService.getValue('debug').openDebug === 'openOnDebugBreak') { + this.viewletService.openViewlet(VIEWLET_ID); + } - if (this.configurationService.getValue('debug').focusWindowOnBreak) { - this.hostService.focus(); - } + if (this.configurationService.getValue('debug').focusWindowOnBreak) { + this.hostService.focus(); } } - }; + } + }; - promises.topCallStack.then(focus); - promises.wholeCallStack.then(() => { - if (!this.debugService.getViewModel().focusedStackFrame) { - // The top stack frame can be deemphesized so try to focus again #68616 - focus(); - } - }); + await promises.topCallStack; + focus(); + await promises.wholeCallStack; + if (!this.debugService.getViewModel().focusedStackFrame) { + // The top stack frame can be deemphesized so try to focus again #68616 + focus(); } - }).then(() => this._onDidChangeState.fire()); + } + this._onDidChangeState.fire(); })); this.rawListeners.push(this.raw.onDidThread(event => { @@ -763,15 +771,21 @@ export class DebugSession implements IDebugSession { } } else if (event.body.reason === 'exited') { this.model.clearThreads(this.getId(), true, event.body.threadId); + const viewModel = this.debugService.getViewModel(); + const focusedThread = viewModel.focusedThread; + if (focusedThread && event.body.threadId === focusedThread.threadId) { + // De-focus the thread in case it was focused + this.debugService.focusStackFrame(undefined, undefined, viewModel.focusedSession, false); + } } })); - this.rawListeners.push(this.raw.onDidTerminateDebugee(event => { + this.rawListeners.push(this.raw.onDidTerminateDebugee(async event => { aria.status(nls.localize('debuggingStopped', "Debugging stopped.")); if (event.body && event.body.restart) { - this.debugService.restartSession(this, event.body.restart).then(undefined, onUnexpectedError); + await this.debugService.restartSession(this, event.body.restart); } else if (this.raw) { - this.raw.disconnect(); + await this.raw.disconnect(); } })); @@ -792,7 +806,7 @@ export class DebugSession implements IDebugSession { })); let outpuPromises: Promise[] = []; - this.rawListeners.push(this.raw.onDidOutput(event => { + this.rawListeners.push(this.raw.onDidOutput(async event => { if (!event.body || !this.raw) { return; } @@ -818,17 +832,21 @@ export class DebugSession implements IDebugSession { } : undefined; if (event.body.variablesReference) { const container = new ExpressionContainer(this, undefined, event.body.variablesReference, generateUuid()); - outpuPromises.push(container.getChildren().then(children => { - return Promise.all(waitFor).then(() => children.forEach(child => { + outpuPromises.push(container.getChildren().then(async children => { + await Promise.all(waitFor); + children.forEach(child => { // Since we can not display multiple trees in a row, we are displaying these variables one after the other (ignoring their names) (child).name = null; this.appendToRepl(child, outputSeverity, source); - })); + }); })); } else if (typeof event.body.output === 'string') { - Promise.all(waitFor).then(() => this.appendToRepl(event.body.output, outputSeverity, source)); + await Promise.all(waitFor); + this.appendToRepl(event.body.output, outputSeverity, source); } - Promise.all(outpuPromises).then(() => outpuPromises = []); + + await Promise.all(outpuPromises); + outpuPromises = []; })); this.rawListeners.push(this.raw.onDidBreakpoint(event => { diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index bc82617dd8..787cd1fff3 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -176,7 +176,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { }); })); - this._register(this.layoutService.onTitleBarVisibilityChange(() => this.setYCoordinate())); + this._register(this.layoutService.onPartVisibilityChange(() => this.setYCoordinate())); this._register(browser.onDidChangeZoomLevel(() => this.setYCoordinate())); } @@ -192,10 +192,10 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { super.updateStyles(); if (this.$el) { - this.$el.style.backgroundColor = this.getColor(debugToolBarBackground); + this.$el.style.backgroundColor = this.getColor(debugToolBarBackground) || ''; const widgetShadowColor = this.getColor(widgetShadow); - this.$el.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : null; + this.$el.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : ''; const contrastBorderColor = this.getColor(contrastBorder); const borderColor = this.getColor(debugToolBarBorder); diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index 3038575b6e..f9835f38df 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -214,7 +214,7 @@ export class DebugViewlet extends ViewContainerViewlet { class ToggleReplAction extends TogglePanelAction { static readonly ID = 'debug.toggleRepl'; - static LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugConsoleAction' }, 'Debug Console'); + static readonly LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugConsoleAction' }, 'Debug Console'); constructor(id: string, label: string, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, diff --git a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts index 277ce7f71e..fd539246de 100644 --- a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts @@ -5,7 +5,6 @@ import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { IDebugHelperService } from 'vs/workbench/contrib/debug/common/debug'; @@ -13,17 +12,20 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; -import { IProcessEnvironment } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; +import { mapToSerializable } from 'vs/base/common/map'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IWorkspaceProvider, IWorkspace } from 'vs/workbench/services/host/browser/browserHostService'; class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient implements IExtensionHostDebugService { + private workspaceProvider: IWorkspaceProvider; + constructor( @IRemoteAgentService remoteAgentService: IRemoteAgentService, - @IEnvironmentService environmentService: IEnvironmentService + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService ) { const connection = remoteAgentService.getConnection(); - let channel: IChannel; if (connection) { channel = connection.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName); @@ -35,11 +37,26 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i super(channel); + if (environmentService.options && environmentService.options.workspaceProvider) { + this.workspaceProvider = environmentService.options.workspaceProvider; + } else { + this.workspaceProvider = { open: async () => undefined, workspace: undefined }; + console.warn('Extension Host Debugging not available due to missing workspace provider.'); + } + + this.registerListeners(environmentService); + } + + private registerListeners(environmentService: IWorkbenchEnvironmentService): void { + + // Reload window on reload request this._register(this.onReload(event => { if (environmentService.isExtensionDevelopment && environmentService.debugExtensionHost.debugId === event.sessionId) { window.location.reload(); } })); + + // Close window on close request this._register(this.onClose(event => { if (environmentService.isExtensionDevelopment && environmentService.debugExtensionHost.debugId === event.sessionId) { window.close(); @@ -47,8 +64,46 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i })); } - openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { - // we pass the "ParsedArgs" as query parameters of the URL + async openExtensionDevelopmentHostWindow(args: string[]): Promise { + if (!this.workspaceProvider.payload) { + // TODO@Ben remove me once environment is adopted + return this.openExtensionDevelopmentHostWindowLegacy(args); + } + + // Find out which workspace to open debug window on + let debugWorkspace: IWorkspace = undefined; + const folderUriArg = this.findArgument('folder-uri', args); + if (folderUriArg) { + debugWorkspace = { folderUri: URI.parse(folderUriArg) }; + } + + // Add environment parameters required for debug to work + const environment = new Map(); + + const extensionDevelopmentPath = this.findArgument('extensionDevelopmentPath', args); + if (extensionDevelopmentPath) { + environment.set('extensionDevelopmentPath', extensionDevelopmentPath); + } + + const debugId = this.findArgument('debugId', args); + if (debugId) { + environment.set('debugId', debugId); + } + + const inspectBrkExtensions = this.findArgument('inspect-brk-extensions', args); + if (inspectBrkExtensions) { + environment.set('inspect-brk-extensions', inspectBrkExtensions); + } + + // Open debug window as new window. Pass ParsedArgs over. + this.workspaceProvider.open(debugWorkspace, { + reuse: false, // debugging always requires a new window + payload: mapToSerializable(environment) // mandatory properties to enable debugging + }); + } + + private async openExtensionDevelopmentHostWindowLegacy(args: string[]): Promise { + // we pass the "args" as query parameters of the URL let newAddress = `${document.location.origin}${document.location.pathname}?`; let gotFolder = false; @@ -61,9 +116,19 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i newAddress += `${key}=${encodeURIComponent(value)}`; }; - const f = args['folder-uri']; + const findArgument = (key: string) => { + for (let a of args) { + const k = `--${key}=`; + if (a.indexOf(k) === 0) { + return a.substr(k.length); + } + } + return undefined; + }; + + const f = findArgument('folder-uri'); if (f) { - const u = URI.parse(f[0]); + const u = URI.parse(f); gotFolder = true; addQueryParameter('folder', u.path); } @@ -72,26 +137,36 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i addQueryParameter('ew', 'true'); } - const ep = args['extensionDevelopmentPath']; + const ep = findArgument('extensionDevelopmentPath'); if (ep) { - let u = ep[0]; - addQueryParameter('edp', u); + addQueryParameter('extensionDevelopmentPath', ep); } - const di = args['debugId']; + const di = findArgument('debugId'); if (di) { - addQueryParameter('di', di); + addQueryParameter('debugId', di); } - const ibe = args['inspect-brk-extensions']; + const ibe = findArgument('inspect-brk-extensions'); if (ibe) { - addQueryParameter('ibe', ibe); + addQueryParameter('inspect-brk-extensions', ibe); } window.open(newAddress); return Promise.resolve(); } + + private findArgument(key: string, args: string[]): string | undefined { + for (const a of args) { + const k = `--${key}=`; + if (a.indexOf(k) === 0) { + return a.substr(k.length); + } + } + + return undefined; + } } registerSingleton(IExtensionHostDebugService, BrowserExtensionHostDebugService); diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index 85d00120cd..21941fdd9c 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -240,7 +240,7 @@ class RootTreeItem extends BaseTreeItem { class SessionTreeItem extends BaseTreeItem { - private static URL_REGEXP = /^(https?:\/\/[^/]+)(\/.*)$/; + private static readonly URL_REGEXP = /^(https?:\/\/[^/]+)(\/.*)$/; private _session: IDebugSession; private _initialized: boolean; 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 38341b722c..48e363422f 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -14,6 +14,7 @@ .debug-breakpoint-hint { background: url('breakpoint-hint.svg') center center no-repeat; + cursor: pointer; } .debug-breakpoint-disabled, @@ -50,7 +51,7 @@ .monaco-editor .debug-breakpoint-placeholder::before, .monaco-editor .debug-top-stack-frame-column::before { content: " "; - width: 1.3em; + width: 0.9em; display: inline-block; vertical-align: text-bottom; margin-right: 2px; @@ -62,9 +63,6 @@ } .monaco-editor .inline-breakpoint-widget { - width: 1.3em; - height: 1.3em; - margin-left: 0.61em; cursor: pointer; } @@ -178,7 +176,7 @@ /* White color when element is selected and list is focused. White looks better on blue selection background. */ .monaco-workbench .monaco-list:focus .monaco-list-row.selected .expression .name, .monaco-workbench .monaco-list:focus .monaco-list-row.selected .expression .value { - color: white; + color: inherit; } .monaco-workbench .monaco-list-row .expression .name { diff --git a/src/vs/workbench/contrib/debug/browser/media/debugHover.css b/src/vs/workbench/contrib/debug/browser/media/debugHover.css index 74cbf74404..79dd892078 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugHover.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugHover.css @@ -22,6 +22,7 @@ padding-left: 15px; padding-right: 2px; font-size: 11px; + line-height: 18px; word-break: normal; text-overflow: ellipsis; height: 18px; diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index 7af7df6c67..50ac7f49df 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -12,7 +12,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { formatPII, isUri } from 'vs/workbench/contrib/debug/common/debugUtils'; import { IDebugAdapter, IConfig, AdapterEndEvent, IDebugger } from 'vs/workbench/contrib/debug/common/debug'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; -import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { URI } from 'vs/base/common/uri'; import { IProcessEnvironment } from 'vs/base/common/platform'; @@ -228,25 +227,23 @@ export class RawDebugSession implements IDisposable { /** * Starts the underlying debug adapter and tracks the session time for telemetry. */ - start(): Promise { + async start(): Promise { if (!this.debugAdapter) { return Promise.reject(new Error('no debug adapter')); } - return this.debugAdapter.startSession().then(() => { - this.startTime = new Date().getTime(); - }, err => { - return Promise.reject(err); - }); + + await this.debugAdapter.startSession(); + this.startTime = new Date().getTime(); } /** * Send client capabilities to the debug adapter and receive DA capabilities in return. */ - initialize(args: DebugProtocol.InitializeRequestArguments): Promise { - return this.send('initialize', args).then((response: DebugProtocol.InitializeResponse) => { - this.mergeCapabilities(response.body); - return response; - }); + async initialize(args: DebugProtocol.InitializeRequestArguments): Promise { + const response = await this.send('initialize', args); + this.mergeCapabilities(response.body); + + return response; } /** @@ -258,11 +255,11 @@ export class RawDebugSession implements IDisposable { //---- DAP requests - launchOrAttach(config: IConfig): Promise { - return this.send(config.request, config).then(response => { - this.mergeCapabilities(response.body); - return response; - }); + async launchOrAttach(config: IConfig): Promise { + const response = await this.send(config.request, config); + this.mergeCapabilities(response.body); + + return response; } /** @@ -286,35 +283,32 @@ export class RawDebugSession implements IDisposable { return Promise.reject(new Error('restart not supported')); } - next(args: DebugProtocol.NextArguments): Promise { - return this.send('next', args).then(response => { - this.fireSimulatedContinuedEvent(args.threadId); - return response; - }); + async next(args: DebugProtocol.NextArguments): Promise { + const response = await this.send('next', args); + this.fireSimulatedContinuedEvent(args.threadId); + return response; } - stepIn(args: DebugProtocol.StepInArguments): Promise { - return this.send('stepIn', args).then(response => { - this.fireSimulatedContinuedEvent(args.threadId); - return response; - }); + async stepIn(args: DebugProtocol.StepInArguments): Promise { + const response = await this.send('stepIn', args); + this.fireSimulatedContinuedEvent(args.threadId); + return response; } - stepOut(args: DebugProtocol.StepOutArguments): Promise { - return this.send('stepOut', args).then(response => { - this.fireSimulatedContinuedEvent(args.threadId); - return response; - }); + async stepOut(args: DebugProtocol.StepOutArguments): Promise { + const response = await this.send('stepOut', args); + this.fireSimulatedContinuedEvent(args.threadId); + return response; } - continue(args: DebugProtocol.ContinueArguments): Promise { - return this.send('continue', args).then(response => { - if (response && response.body && response.body.allThreadsContinued !== undefined) { - this.allThreadsContinued = response.body.allThreadsContinued; - } - this.fireSimulatedContinuedEvent(args.threadId, this.allThreadsContinued); - return response; - }); + async continue(args: DebugProtocol.ContinueArguments): Promise { + const response = await this.send('continue', args); + if (response && response.body && response.body.allThreadsContinued !== undefined) { + this.allThreadsContinued = response.body.allThreadsContinued; + } + this.fireSimulatedContinuedEvent(args.threadId, this.allThreadsContinued); + + return response; } pause(args: DebugProtocol.PauseArguments): Promise { @@ -335,12 +329,11 @@ export class RawDebugSession implements IDisposable { return Promise.reject(new Error('setVariable not supported')); } - restartFrame(args: DebugProtocol.RestartFrameArguments, threadId: number): Promise { + async restartFrame(args: DebugProtocol.RestartFrameArguments, threadId: number): Promise { if (this.capabilities.supportsRestartFrame) { - return this.send('restartFrame', args).then(response => { - this.fireSimulatedContinuedEvent(threadId); - return response; - }); + const response = await this.send('restartFrame', args); + this.fireSimulatedContinuedEvent(threadId); + return response; } return Promise.reject(new Error('restartFrame not supported')); } @@ -433,26 +426,24 @@ export class RawDebugSession implements IDisposable { return this.send('evaluate', args); } - stepBack(args: DebugProtocol.StepBackArguments): Promise { + async stepBack(args: DebugProtocol.StepBackArguments): Promise { if (this.capabilities.supportsStepBack) { - return this.send('stepBack', args).then(response => { - if (response.body === undefined) { // TODO@AW why this check? - this.fireSimulatedContinuedEvent(args.threadId); - } - return response; - }); + const response = await this.send('stepBack', args); + if (response.body === undefined) { // TODO@AW why this check? + this.fireSimulatedContinuedEvent(args.threadId); + } + return response; } return Promise.reject(new Error('stepBack not supported')); } - reverseContinue(args: DebugProtocol.ReverseContinueArguments): Promise { + async reverseContinue(args: DebugProtocol.ReverseContinueArguments): Promise { if (this.capabilities.supportsStepBack) { - return this.send('reverseContinue', args).then(response => { - if (response.body === undefined) { // TODO@AW why this check? - this.fireSimulatedContinuedEvent(args.threadId); - } - return response; - }); + const response = await this.send('reverseContinue', args); + if (response.body === undefined) { // TODO@AW why this check? + this.fireSimulatedContinuedEvent(args.threadId); + } + return response; } return Promise.reject(new Error('reverseContinue not supported')); } @@ -464,13 +455,13 @@ export class RawDebugSession implements IDisposable { return Promise.reject(new Error('gotoTargets is not supported')); } - goto(args: DebugProtocol.GotoArguments): Promise { + async goto(args: DebugProtocol.GotoArguments): Promise { if (this.capabilities.supportsGotoTargetsRequest) { - return this.send('goto', args).then(res => { - this.fireSimulatedContinuedEvent(args.threadId); - return res; - }); + const response = await this.send('goto', args); + this.fireSimulatedContinuedEvent(args.threadId); + return response; } + return Promise.reject(new Error('goto is not supported')); } @@ -484,36 +475,32 @@ export class RawDebugSession implements IDisposable { //---- private - private shutdown(error?: Error, restart = false): Promise { + private async shutdown(error?: Error, restart = false): Promise { if (!this.inShutdown) { this.inShutdown = true; if (this.debugAdapter) { - return this.send('disconnect', { restart }, undefined, 500).then(() => { + try { + await this.send('disconnect', { restart }, undefined, 500); + } finally { this.stopAdapter(error); - }, () => { - // ignore error - this.stopAdapter(error); - }); + } + } else { + return this.stopAdapter(error); } - return this.stopAdapter(error); } - return Promise.resolve(undefined); } - private stopAdapter(error?: Error): Promise { - if (this.debugAdapter) { - const da = this.debugAdapter; - this.debugAdapter = null; - return da.stopSession().then(_ => { + private async stopAdapter(error?: Error): Promise { + try { + if (this.debugAdapter) { + const da = this.debugAdapter; + this.debugAdapter = null; + await da.stopSession(); this.debugAdapterStopped = true; - this.fireAdapterExitEvent(error); - }, err => { - this.fireAdapterExitEvent(error); - }); - } else { + } + } finally { this.fireAdapterExitEvent(error); } - return Promise.resolve(undefined); } private fireAdapterExitEvent(error?: Error): void { @@ -557,18 +544,19 @@ export class RawDebugSession implements IDisposable { }); break; case 'runInTerminal': - dbgr.runInTerminal(request.arguments as DebugProtocol.RunInTerminalRequestArguments).then(shellProcessId => { + try { + const shellProcessId = await dbgr.runInTerminal(request.arguments as DebugProtocol.RunInTerminalRequestArguments); const resp = response as DebugProtocol.RunInTerminalResponse; resp.body = {}; if (typeof shellProcessId === 'number') { resp.body.shellProcessId = shellProcessId; } safeSendResponse(resp); - }, err => { + } catch (err) { response.success = false; response.message = err.message; safeSendResponse(response); - }); + } break; default: response.success = false; @@ -580,9 +568,7 @@ export class RawDebugSession implements IDisposable { private launchVsCode(vscodeArgs: ILaunchVSCodeArguments): Promise { - let args: ParsedArgs = { - _: [] - }; + const args: string[] = []; for (let arg of vscodeArgs.args) { if (arg.prefix) { @@ -594,32 +580,11 @@ export class RawDebugSession implements IDisposable { if ((key === 'file-uri' || key === 'folder-uri') && !isUri(arg.path)) { value = URI.file(value).toString(); - - const v = args[key]; - if (v) { - v.push(value); - } else { - args[key] = [value]; - } - } else if (key === 'extensionDevelopmentPath' || key === 'enable-proposed-api') { - const v = args[key]; - if (v) { - v.push(value); - } else { - args[key] = [value]; - } - } else { - (args)[key] = value; } + args.push(`--${key}=${value}`); } else { - const match = /^--(.+)$/.exec(a2); - if (match && match.length === 2) { - const key = match[1]; - (args)[key] = true; - } else { - args._.push(a2); - } + args.push(a2); } } } diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index c20a38d441..6f16f3b4d9 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -46,7 +46,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { Variable } from 'vs/workbench/contrib/debug/common/debugModel'; import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel'; -import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { CachedListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { renderExpressionValue, AbstractExpressionsRenderer, IExpressionTemplateData, renderVariable, IInputBoxOptions } from 'vs/workbench/contrib/debug/browser/baseDebugView'; @@ -74,8 +74,8 @@ interface IPrivateReplService { _serviceBrand: undefined; acceptReplInput(): void; getVisibleContent(): string; - selectSession(session?: IDebugSession): void; - clearRepl(): void; + selectSession(session?: IDebugSession): Promise; + clearRepl(): Promise; focusRepl(): void; } @@ -129,7 +129,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati } private registerListeners(): void { - this._register(this.debugService.getViewModel().onDidFocusSession(session => { + this._register(this.debugService.getViewModel().onDidFocusSession(async session => { if (session) { sessionsToIgnore.delete(session); if (this.completionItemProvider) { @@ -159,13 +159,13 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati } } - this.selectSession(); + await this.selectSession(); })); - this._register(this.debugService.onWillNewSession(newSession => { + this._register(this.debugService.onWillNewSession(async 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(newSession); + await this.selectSession(newSession); } this.updateTitleArea(); })); @@ -252,7 +252,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati } } - selectSession(session?: IDebugSession): void { + async selectSession(session?: IDebugSession): Promise { const treeInput = this.tree.getInput(); if (!session) { const focusedSession = this.debugService.getViewModel().focusedSession; @@ -272,7 +272,8 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati }); if (this.tree && treeInput !== session) { - this.tree.setInput(session).then(() => revealLastElement(this.tree)).then(undefined, errors.onUnexpectedError); + await this.tree.setInput(session); + revealLastElement(this.tree); } } @@ -280,14 +281,14 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.updateInputDecoration(); } - clearRepl(): void { + async clearRepl(): Promise { const session = this.tree.getInput(); if (session) { session.removeReplExpressions(); if (session.state === State.Inactive) { // Ignore inactive sessions which got cleared - so they are not shown any more sessionsToIgnore.add(session); - this.selectSession(); + await this.selectSession(); this.updateTitleArea(); } } @@ -646,7 +647,7 @@ class ReplEvaluationResultsRenderer implements ITreeRenderer { +class ReplDelegate extends CachedListVirtualDelegate { - constructor(private configurationService: IConfigurationService) { } + constructor(private configurationService: IConfigurationService) { + super(); + } getHeight(element: IReplElement): number { - const countNumberOfLines = (str: string) => Math.max(1, (str && str.match(/\r\n|\n/g) || []).length); - - // Give approximate heights. Repl has dynamic height so the tree will measure the actual height on its own. const config = this.configurationService.getValue('debug'); - const fontSize = config.console.fontSize; - const rowHeight = Math.ceil(1.4 * fontSize); - const wordWrap = config.console.wordWrap; - if (!wordWrap) { - return rowHeight; + + if (!config.console.wordWrap) { + return Math.ceil(1.4 * config.console.fontSize); } - // In order to keep scroll position we need to give a good approximation to the tree - // For every 150 characters increase the number of lines needed - if (element instanceof ReplEvaluationResult) { + return super.getHeight(element); + } + + protected estimateHeight(element: IReplElement): number { + const config = this.configurationService.getValue('debug'); + const rowHeight = Math.ceil(1.4 * config.console.fontSize); + const countNumberOfLines = (str: string) => Math.max(1, (str && str.match(/\r\n|\n/g) || []).length); + const hasValue = (e: any): e is { value: string } => typeof e.value === 'string'; + + // Calculate a rough overestimation for the height + // For every 30 characters increase the number of lines needed + if (hasValue(element)) { let value = element.value; - - if (element.hasChildren) { - return rowHeight; - } - - let valueRows = value ? (countNumberOfLines(value) + Math.floor(value.length / 150)) : 0; - return rowHeight * valueRows; - } - - if (element instanceof SimpleReplElement || element instanceof ReplEvaluationInput) { - let value = element.value; - let valueRows = countNumberOfLines(value) + Math.floor(value.length / 150); + let valueRows = countNumberOfLines(value) + Math.floor(value.length / 30); return valueRows * rowHeight; } @@ -851,7 +847,7 @@ class ReplDelegate implements IListVirtualDelegate { return ReplRawObjectsRenderer.ID; } - hasDynamicHeight?(element: IReplElement): boolean { + hasDynamicHeight(element: IReplElement): boolean { // Empty elements should not have dynamic height since they will be invisible return element.toString().length > 0; } @@ -919,7 +915,7 @@ class AcceptReplInputAction extends EditorAction { } run(accessor: ServicesAccessor, editor: ICodeEditor): void | Promise { - SuggestController.get(editor).acceptSelectedSuggestion(); + SuggestController.get(editor).acceptSelectedSuggestion(false, true); accessor.get(IPrivateReplService).acceptReplInput(); } } @@ -941,7 +937,7 @@ class FilterReplAction extends EditorAction { } run(accessor: ServicesAccessor, editor: ICodeEditor): void | Promise { - SuggestController.get(editor).acceptSelectedSuggestion(); + SuggestController.get(editor).acceptSelectedSuggestion(false, true); accessor.get(IPrivateReplService).focusRepl(); } } @@ -984,7 +980,7 @@ class SelectReplActionViewItem extends FocusSessionActionViewItem { class SelectReplAction extends Action { static readonly ID = 'workbench.action.debug.selectRepl'; - static LABEL = nls.localize('selectRepl', "Select Debug Console"); + static readonly LABEL = nls.localize('selectRepl', "Select Debug Console"); constructor(id: string, label: string, @IDebugService private readonly debugService: IDebugService, @@ -993,12 +989,12 @@ class SelectReplAction extends Action { super(id, label); } - run(session: IDebugSession): Promise { + async run(session: IDebugSession): Promise { // If session is already the focused session we need to manualy update the tree since view model will not send a focused change event if (session && session.state !== State.Inactive && session !== this.debugService.getViewModel().focusedSession) { - this.debugService.focusStackFrame(undefined, undefined, session, true); + await this.debugService.focusStackFrame(undefined, undefined, session, true); } else { - this.replService.selectSession(session); + await this.replService.selectSession(session); } return Promise.resolve(undefined); @@ -1007,7 +1003,7 @@ class SelectReplAction extends Action { export class ClearReplAction extends Action { static readonly ID = 'workbench.debug.panel.action.clearReplAction'; - static LABEL = nls.localize('clearRepl', "Clear Console"); + static readonly LABEL = nls.localize('clearRepl', "Clear Console"); constructor(id: string, label: string, @IPanelService private readonly panelService: IPanelService @@ -1015,11 +1011,9 @@ export class ClearReplAction extends Action { super(id, label, 'debug-action codicon-clear-all'); } - run(): Promise { + async run(): Promise { const repl = this.panelService.openPanel(REPL_ID); - repl.clearRepl(); + await repl.clearRepl(); aria.status(nls.localize('debugConsoleCleared', "Debug console was cleared")); - - return Promise.resolve(undefined); } } diff --git a/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts index 625808128e..db9aa14ac1 100644 --- a/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts +++ b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts @@ -12,6 +12,7 @@ import { IDebugService, State } from 'vs/workbench/contrib/debug/common/debug'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_BACKGROUND, Themable, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER, STATUS_BAR_BORDER } from 'vs/workbench/common/theme'; import { addClass, removeClass, createStyleSheet } from 'vs/base/browser/dom'; +import { assertIsDefined } from 'vs/base/common/types'; // colors for theming @@ -56,7 +57,7 @@ export class StatusBarColorProvider extends Themable implements IWorkbenchContri protected updateStyles(): void { super.updateStyles(); - const container = this.layoutService.getContainer(Parts.STATUSBAR_PART); + const container = assertIsDefined(this.layoutService.getContainer(Parts.STATUSBAR_PART)); if (isStatusbarInDebugMode(this.debugService)) { addClass(container, 'debugging'); } else { @@ -65,7 +66,7 @@ export class StatusBarColorProvider extends Themable implements IWorkbenchContri // Container Colors const backgroundColor = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_DEBUGGING_BACKGROUND, STATUS_BAR_BACKGROUND)); - container.style.backgroundColor = backgroundColor; + container.style.backgroundColor = backgroundColor || ''; container.style.color = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_DEBUGGING_FOREGROUND, STATUS_BAR_FOREGROUND)); // Border Color @@ -108,7 +109,7 @@ export function isStatusbarInDebugMode(debugService: IDebugService): boolean { } const session = debugService.getViewModel().focusedSession; - const isRunningWithoutDebug = session && session.configuration && session.configuration.noDebug; + const isRunningWithoutDebug = session?.configuration?.noDebug; if (isRunningWithoutDebug) { return false; } diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index bcf0a80083..98b9cf1c16 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -25,7 +25,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { Emitter } from 'vs/base/common/event'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; -import { onUnexpectedError } from 'vs/base/common/errors'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -57,28 +56,27 @@ export class VariablesView extends ViewletPanel { super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); // Use scheduler to prevent unnecessary flashing - this.onFocusStackFrameScheduler = new RunOnceScheduler(() => { + this.onFocusStackFrameScheduler = new RunOnceScheduler(async () => { const stackFrame = this.debugService.getViewModel().focusedStackFrame; this.needsRefresh = false; if (stackFrame && this.savedViewState) { - this.tree.setInput(this.debugService.getViewModel(), this.savedViewState).then(null, onUnexpectedError); + await this.tree.setInput(this.debugService.getViewModel(), this.savedViewState); this.savedViewState = undefined; } else { if (!stackFrame) { // We have no stackFrame, save tree state before it is cleared this.savedViewState = this.tree.getViewState(); } - this.tree.updateChildren().then(() => { - if (stackFrame) { - stackFrame.getScopes().then(scopes => { - // Expand the first scope if it is not expensive and if there is no expansion state (all are collapsed) - if (scopes.every(s => this.tree.getNode(s).collapsed) && scopes.length > 0 && !scopes[0].expensive) { - this.tree.expand(scopes[0]).then(undefined, onUnexpectedError); - } - }); + await this.tree.updateChildren(); + if (stackFrame) { + const scopes = await stackFrame.getScopes(); + // Expand the first scope if it is not expensive and if there is no expansion state (all are collapsed) + if (scopes.every(s => this.tree.getNode(s).collapsed) && scopes.length > 0 && !scopes[0].expensive) { + this.tree.expand(scopes[0]); } - }, onUnexpectedError); + } + } }, 400); } @@ -96,12 +94,14 @@ export class VariablesView extends ViewletPanel { keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IExpression | IScope) => e } }); - this.tree.setInput(this.debugService.getViewModel()).then(null, onUnexpectedError); + this.tree.setInput(this.debugService.getViewModel()); CONTEXT_VARIABLES_FOCUSED.bindTo(this.tree.contextKeyService); - const collapseAction = new CollapseAction(this.tree, true, 'explorer-action collapse-explorer'); - this.toolbar.setActions([collapseAction])(); + if (this.toolbar) { + const collapseAction = new CollapseAction(this.tree, true, 'explorer-action collapse-explorer'); + this.toolbar.setActions([collapseAction])(); + } this.tree.updateChildren(); this._register(this.debugService.getViewModel().onDidFocusStackFrame(sf => { diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 803fe02cb1..0f5f58a83c 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -24,7 +24,6 @@ import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { IAsyncDataSource, ITreeMouseEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction } from 'vs/base/browser/ui/tree/tree'; import { IDragAndDropData } from 'vs/base/browser/dnd'; -import { onUnexpectedError } from 'vs/base/common/errors'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { FuzzyScore } from 'vs/base/common/filters'; import { IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; @@ -71,13 +70,15 @@ export class WatchExpressionsView extends ViewletPanel { dnd: new WatchExpressionsDragAndDrop(this.debugService), }); - this.tree.setInput(this.debugService).then(undefined, onUnexpectedError); + this.tree.setInput(this.debugService); CONTEXT_WATCH_EXPRESSIONS_FOCUSED.bindTo(this.tree.contextKeyService); - const addWatchExpressionAction = new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService); - const collapseAction = new CollapseAction(this.tree, true, 'explorer-action collapse-explorer'); - const removeAllWatchExpressionsAction = new RemoveAllWatchExpressionsAction(RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL, this.debugService, this.keybindingService); - this.toolbar.setActions([addWatchExpressionAction, collapseAction, removeAllWatchExpressionsAction])(); + if (this.toolbar) { + const addWatchExpressionAction = new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService); + const collapseAction = new CollapseAction(this.tree, true, 'explorer-action collapse-explorer'); + const removeAllWatchExpressionsAction = new RemoveAllWatchExpressionsAction(RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL, this.debugService, this.keybindingService); + this.toolbar.setActions([addWatchExpressionAction, collapseAction, removeAllWatchExpressionsAction])(); + } this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); this._register(this.tree.onMouseDblClick(e => this.onMouseDblClick(e))); @@ -152,7 +153,7 @@ export class WatchExpressionsView extends ViewletPanel { return Promise.resolve(); })); if (!expression.hasChildren) { - actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, expression.value, 'watch', this.debugService)); + actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, expression.value, 'watch')); } actions.push(new Separator()); @@ -166,7 +167,7 @@ export class WatchExpressionsView extends ViewletPanel { if (element instanceof Variable) { const variable = element as Variable; if (!variable.hasChildren) { - actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable, 'watch', this.debugService)); + actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable, 'watch')); } actions.push(new Separator()); } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 4ec6d97c11..e42d6f6ae1 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -160,7 +160,7 @@ export interface IDebugSession extends ITreeElement { readonly configuration: IConfig; readonly unresolvedConfiguration: IConfig | undefined; readonly state: State; - readonly root: IWorkspaceFolder; + readonly root: IWorkspaceFolder | undefined; readonly parentSession: IDebugSession | undefined; readonly subId: string | undefined; @@ -467,6 +467,7 @@ export interface IDebugConfiguration { }; focusWindowOnBreak: boolean; onTaskErrors: 'debugAnyway' | 'showErrors' | 'prompt'; + showBreakpointsInOverviewRuler: boolean; } export interface IGlobalConfig { @@ -732,7 +733,7 @@ export interface IDebugService { /** * Sets the focused stack frame and evaluates all expressions against the newly focused stack frame, */ - focusStackFrame(focusedStackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession, explicit?: boolean): void; + focusStackFrame(focusedStackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession, explicit?: boolean): Promise; /** * Adds new breakpoints to the model for the file specified with the uri. Notifies debug adapter of breakpoint changes. diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index ed36abecf4..6abab23082 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -27,7 +27,7 @@ import { mixin } from 'vs/base/common/objects'; export class ExpressionContainer implements IExpressionContainer { - public static allValues = new Map(); + public static readonly allValues = new Map(); // Use chunks to support variable paging #9537 private static readonly BASE_CHUNK_SIZE = 100; @@ -65,7 +65,7 @@ export class ExpressionContainer implements IExpressionContainer { private async doGetChildren(): Promise { if (!this.hasChildren) { - return Promise.resolve([]); + return []; } if (!this.getChildrenInChunks) { @@ -114,13 +114,16 @@ export class ExpressionContainer implements IExpressionContainer { return !!this.reference && this.reference > 0; } - private fetchVariables(start: number | undefined, count: number | undefined, filter: 'indexed' | 'named' | undefined): Promise { - return this.session!.variables(this.reference || 0, this.threadId, filter, start, count).then(response => { + private async fetchVariables(start: number | undefined, count: number | undefined, filter: 'indexed' | 'named' | undefined): Promise { + try { + const response = await this.session!.variables(this.reference || 0, this.threadId, filter, start, count); return response && response.body && response.body.variables ? distinct(response.body.variables.filter(v => !!v && isString(v.name)), (v: DebugProtocol.Variable) => v.name).map((v: DebugProtocol.Variable) => new Variable(this.session, this.threadId, this, v.variablesReference, v.name, v.evaluateName, v.value, v.namedVariables, v.indexedVariables, v.presentationHint, v.type)) : []; - }, (e: Error) => [new Variable(this.session, this.threadId, this, 0, e.message, e.message, '', 0, 0, { kind: 'virtual' }, undefined, false)]); + } catch (e) { + return [new Variable(this.session, this.threadId, this, 0, e.message, e.message, '', 0, 0, { kind: 'virtual' }, undefined, false)]; + } } // The adapter explicitly sents the children count of an expression only if there are lots of children which should be chunked. @@ -172,7 +175,7 @@ export class ExpressionContainer implements IExpressionContainer { } export class Expression extends ExpressionContainer implements IExpression { - static DEFAULT_VALUE = nls.localize('notAvailable', "not available"); + static readonly DEFAULT_VALUE = nls.localize('notAvailable', "not available"); public available: boolean; @@ -221,7 +224,7 @@ export class Variable extends ExpressionContainer implements IExpression { async setVariable(value: string): Promise { if (!this.session) { - return Promise.resolve(undefined); + return; } try { @@ -313,18 +316,17 @@ export class StackFrame implements IStackFrame { return (from > 0 ? '...' : '') + this.source.uri.path.substr(from); } - getMostSpecificScopes(range: IRange): Promise { - return this.getScopes().then(scopes => { - scopes = scopes.filter(s => !s.expensive); - const haveRangeInfo = scopes.some(s => !!s.range); - if (!haveRangeInfo) { - return scopes; - } + async getMostSpecificScopes(range: IRange): Promise { + const scopes = await this.getScopes(); + const nonExpensiveScopes = scopes.filter(s => !s.expensive); + const haveRangeInfo = nonExpensiveScopes.some(s => !!s.range); + if (!haveRangeInfo) { + return nonExpensiveScopes; + } - const scopesContainingRange = scopes.filter(scope => scope.range && Range.containsRange(scope.range, range)) - .sort((first, second) => (first.range!.endLineNumber - first.range!.startLineNumber) - (second.range!.endLineNumber - second.range!.startLineNumber)); - return scopesContainingRange.length ? scopesContainingRange : scopes; - }); + const scopesContainingRange = nonExpensiveScopes.filter(scope => scope.range && Range.containsRange(scope.range, range)) + .sort((first, second) => (first.range!.endLineNumber - first.range!.startLineNumber) - (second.range!.endLineNumber - second.range!.startLineNumber)); + return scopesContainingRange.length ? scopesContainingRange : nonExpensiveScopes; } restart(): Promise { @@ -342,9 +344,11 @@ 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 { - return !this.source.available ? Promise.resolve(undefined) : - this.source.openInEditor(editorService, this.range, preserveFocus, sideBySide, pinned); + async openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { + if (this.source.available) { + return this.source.openInEditor(editorService, this.range, preserveFocus, sideBySide, pinned); + } + return undefined; } equals(other: IStackFrame): boolean { @@ -399,23 +403,21 @@ export class Thread implements IThread { * Only fetches the first stack frame for performance reasons. Calling this method consecutive times * gets the remainder of the call stack. */ - fetchCallStack(levels = 20): Promise { - if (!this.stopped) { - return Promise.resolve(undefined); - } - - const start = this.callStack.length; - return this.getCallStackImpl(start, levels).then(callStack => { + async fetchCallStack(levels = 20): Promise { + if (this.stopped) { + const start = this.callStack.length; + const callStack = await this.getCallStackImpl(start, levels); if (start < this.callStack.length) { // Set the stack frames for exact position we requested. To make sure no concurrent requests create duplicate stack frames #30660 this.callStack.splice(start, this.callStack.length - start); } this.callStack = this.callStack.concat(callStack || []); - }); + } } - private getCallStackImpl(startFrame: number, levels: number): Promise { - return this.session.stackTrace(this.threadId, startFrame, levels).then(response => { + private async getCallStackImpl(startFrame: number, levels: number): Promise { + try { + const response = await this.session.stackTrace(this.threadId, startFrame, levels); if (!response || !response.body) { return []; } @@ -434,13 +436,13 @@ export class Thread implements IThread { rsf.endColumn || rsf.column ), startFrame + index); }); - }, (err: Error) => { + } catch (err) { if (this.stoppedDetails) { this.stoppedDetails.framesErrorMessage = err.message; } return []; - }); + } } /** @@ -617,8 +619,7 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { } get column(): number | undefined { - // Only respect the column if the user explictly set the column to have an inline breakpoint - return this.verified && this.data && typeof this.data.column === 'number' && typeof this._column === 'number' ? this.data.column : this._column; + return this.verified && this.data && typeof this.data.column === 'number' ? this.data.column : this._column; } get message(): string | undefined { diff --git a/src/vs/workbench/contrib/debug/common/debugUtils.ts b/src/vs/workbench/contrib/debug/common/debugUtils.ts index c24e62c78a..d6ca8b95b8 100644 --- a/src/vs/workbench/contrib/debug/common/debugUtils.ts +++ b/src/vs/workbench/contrib/debug/common/debugUtils.ts @@ -4,32 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { equalsIgnoreCase } from 'vs/base/common/strings'; -import { IConfig, IDebuggerContribution, IDebugService, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; +import { IConfig, IDebuggerContribution, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; import { URI as uri } from 'vs/base/common/uri'; import { isAbsolute } from 'vs/base/common/path'; import { deepClone } from 'vs/base/common/objects'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { first } from 'vs/base/common/arrays'; const _formatPIIRegexp = /{([^}]+)}/g; -export function startDebugging(debugService: IDebugService, historyService: IHistoryService, noDebug: boolean, ): Promise { - const configurationManager = debugService.getConfigurationManager(); - let launch = configurationManager.selectedConfiguration.launch; - if (!launch || launch.getConfigurationNames().length === 0) { - const rootUri = historyService.getLastActiveWorkspaceRoot(); - launch = configurationManager.getLaunch(rootUri); - if (!launch || launch.getConfigurationNames().length === 0) { - const launches = configurationManager.getLaunches(); - launch = first(launches, l => !!(l && l.getConfigurationNames().length), launch); - } - - configurationManager.selectConfiguration(launch); - } - - return debugService.startDebugging(launch, undefined, { noDebug }); -} - export function formatPII(value: string, excludePII: boolean, args: { [key: string]: string }): string { return value.replace(_formatPIIRegexp, function (match, group) { if (excludePII && group.length > 0 && group[0] !== '_') { @@ -196,6 +177,9 @@ function convertPaths(msg: DebugProtocol.ProtocolMessage, fixSourcePath: (toDA: case 'setBreakpoints': fixSourcePath(true, (request.arguments).source); break; + case 'breakpointLocations': + fixSourcePath(true, (request.arguments).source); + break; case 'source': fixSourcePath(true, (request.arguments).source); break; diff --git a/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts index a35dc0a7c7..2f4212365d 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts @@ -8,7 +8,6 @@ import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHo import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; import { IProcessEnvironment } from 'vs/base/common/platform'; -import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { IElectronService } from 'vs/platform/electron/node/electron'; export class ExtensionHostDebugService extends ExtensionHostDebugChannelClient { @@ -20,7 +19,7 @@ export class ExtensionHostDebugService extends ExtensionHostDebugChannelClient { super(mainProcessService.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName)); } - openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { + openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment): Promise { // TODO@Isidor move into debug IPC channel (https://github.com/microsoft/vscode/issues/81060) return this.electronService.openExtensionDevelopmentHostWindow(args, env); } diff --git a/src/vs/workbench/contrib/debug/node/debugAdapter.ts b/src/vs/workbench/contrib/debug/node/debugAdapter.ts index f1aad47535..d9b26f582d 100644 --- a/src/vs/workbench/contrib/debug/node/debugAdapter.ts +++ b/src/vs/workbench/contrib/debug/node/debugAdapter.ts @@ -133,7 +133,6 @@ export class SocketDebugAdapter extends StreamDebugAdapter { this.socket.end(); this.socket = undefined; } - return Promise.resolve(undefined); } } @@ -178,7 +177,6 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { if (options.env) { env = objects.mixin(env, options.env); } - delete env.VSCODE_PREVENT_FOREIGN_INSPECT; if (command === 'node') { if (Array.isArray(args) && args.length > 0) { diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index 5683a13f05..32f31799d5 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -148,7 +148,7 @@ export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments if (value === null) { command += `set "${key}=" && `; } else { - value = value.replace(/[\^\&]/g, s => `^${s}`); + value = value.replace(/[\^\&\|\<\>]/g, s => `^${s}`); command += `set "${key}=${value}" && `; } } 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 006d00079c..57359d2b14 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts @@ -30,7 +30,7 @@ suite('Debug - ANSI Handling', () => { */ setup(() => { model = new DebugModel([], [], [], [], [], { isDirty: (e: any) => false }); - session = new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService); + session = new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService); const instantiationService: TestInstantiationService = workbenchInstantiationService(); linkDetector = instantiationService.createInstance(LinkDetector); diff --git a/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts index 1165ff5951..acb61c6980 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts @@ -16,7 +16,7 @@ import { IBreakpointUpdateData, IDebugSessionOptions } from 'vs/workbench/contri import { NullOpenerService } from 'vs/platform/opener/common/opener'; function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession { - return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService); + return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService); } suite('Debug - Model', () => { diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index 35973a4697..381d311cb8 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -40,7 +40,8 @@ export class MockDebugService implements IDebugService { throw new Error('not implemented'); } - public focusStackFrame(focusedStackFrame: IStackFrame): void { + public focusStackFrame(focusedStackFrame: IStackFrame): Promise { + throw new Error('not implemented'); } sendAllBreakpoints(session?: IDebugSession): Promise { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index af0f5f6408..717c3658e2 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -600,7 +600,6 @@ export class ExtensionEditor extends BaseEditor { this.contentDisposables.add(webviewElement.onDidFocus(() => this.fireOnDidFocus())); const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout: () => { - webviewElement.layout(); webviewElement.layoutWebviewOverElement(template.content); } }); @@ -867,7 +866,8 @@ export class ExtensionEditor extends BaseEditor { this.renderViewContainers(content, manifest, layout), this.renderViews(content, manifest, layout), this.renderLocalizations(content, manifest, layout), - renderDashboardContributions(content, manifest, layout) // {{SQL CARBON EDIT}} + renderDashboardContributions(content, manifest, layout), // {{SQL CARBON EDIT}} + this.renderCustomEditors(content, manifest, layout), ]; scrollableContent.scanDomNode(); @@ -1067,6 +1067,31 @@ export class ExtensionEditor extends BaseEditor { return true; } + private renderCustomEditors(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { + const webviewEditors = (manifest.contributes && manifest.contributes.webviewEditors) || []; + if (!webviewEditors.length) { + return false; + } + + const details = $('details', { open: true, ontoggle: onDetailsToggle }, + $('summary', { tabindex: '0' }, localize('customEditors', "Custom Editors ({0})", webviewEditors.length)), + $('table', undefined, + $('tr', undefined, + $('th', undefined, localize('customEditors view type', "View Type")), + $('th', undefined, localize('customEditors priority', "Priority")), + $('th', undefined, localize('customEditors filenamePattern', "Filename Pattern"))), + ...webviewEditors.map(webviewEditor => + $('tr', undefined, + $('td', undefined, webviewEditor.viewType), + $('td', undefined, webviewEditor.priority), + $('td', undefined, arrays.coalesce(webviewEditor.selector.map(x => x.filenamePattern)).join(', ')))) + ) + ); + + append(container, details); + return true; + } + private renderColorThemes(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { const contributes = manifest.contributes; const contrib = contributes && contributes.themes || []; @@ -1339,12 +1364,10 @@ export class ExtensionEditor extends BaseEditor { private loadContents(loadingTask: () => CacheResult, template: IExtensionEditorTemplate): Promise { addClass(template.content, 'loading'); - const result = loadingTask(); + const result = this.contentDisposables.add(loadingTask()); const onDone = () => removeClass(template.content, 'loading'); result.promise.then(onDone, onDone); - this.contentDisposables.add(toDisposable(() => result.dispose())); - return result.promise; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts b/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts index 3723eac3b3..de3dffb3df 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts @@ -87,7 +87,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe private _allIgnoredRecommendations: string[] = []; private _globallyIgnoredRecommendations: string[] = []; private _workspaceIgnoredRecommendations: string[] = []; - private _extensionsRecommendationsUrl: string; + private _extensionsRecommendationsUrl: string | undefined; public loadWorkspaceConfigPromise: Promise; private proactiveRecommendationsFetched: boolean = false; @@ -120,9 +120,10 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe ) { super(); - // {{SQL CARBON EDIT}} - let extensionPolicy: string = this.configurationService.getValue(ExtensionsPolicyKey); + const extensionPolicy = this.configurationService.getValue(ExtensionsPolicyKey); // {{SQL CARBON EDIT}} if (!this.isEnabled() || extensionPolicy === ExtensionsPolicy.allowNone) { + this.sessionSeed = 0; + this.loadWorkspaceConfigPromise = Promise.resolve(); return; } @@ -533,7 +534,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe const tip = this._importantExeBasedRecommendations[extensionId]; /* __GDPR__ - exeExtensionRecommendations:alreadyInstalled" : { + "exeExtensionRecommendations:alreadyInstalled" : { "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }, "exeName": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index aed1cdae42..8496d67762 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -146,8 +146,8 @@ export abstract class ExtensionAction extends Action implements IExtensionContai export class InstallAction extends ExtensionAction { - private static INSTALL_LABEL = localize('install', "Install"); - private static INSTALLING_LABEL = localize('installing', "Installing"); + private static readonly INSTALL_LABEL = localize('install', "Install"); + private static readonly INSTALLING_LABEL = localize('installing', "Installing"); private static readonly Class = 'extension-action prominent install'; private static readonly InstallingClass = 'extension-action install installing'; @@ -289,8 +289,8 @@ export class InstallAction extends ExtensionAction { export abstract class InstallInOtherServerAction extends ExtensionAction { - protected static INSTALL_LABEL = localize('install', "Install"); - protected static INSTALLING_LABEL = localize('installing', "Installing"); + protected static readonly INSTALL_LABEL = localize('install', "Install"); + protected static readonly INSTALLING_LABEL = localize('installing', "Installing"); private static readonly Class = 'extension-action prominent install'; private static readonly InstallingClass = 'extension-action install installing'; @@ -586,7 +586,7 @@ export class ExtensionActionViewItem extends ActionViewItem { updateEnabled(): void { super.updateEnabled(); - if (this.options.tabOnlyOnFocus && this.getAction().enabled && !this._hasFocus) { + if (this.label && this.options.tabOnlyOnFocus && this.getAction().enabled && !this._hasFocus) { DOM.removeTabIndexAndUpdateFocus(this.label); } } @@ -597,7 +597,7 @@ export class ExtensionActionViewItem extends ActionViewItem { return; } this._hasFocus = value; - if (this.getAction().enabled) { + if (this.label && this.getAction().enabled) { if (this._hasFocus) { this.label.tabIndex = 0; } else { @@ -746,7 +746,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction { export class InstallAnotherVersionAction extends ExtensionAction { static readonly ID = 'workbench.extensions.action.install.anotherVersion'; - static LABEL = localize('install another version', "Install Another Version..."); + static readonly LABEL = localize('install another version', "Install Another Version..."); constructor( @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @@ -852,7 +852,7 @@ export class ExtensionSettingsAction extends ExtensionAction { export class EnableForWorkspaceAction extends ExtensionAction { static readonly ID = 'extensions.enableForWorkspace'; - static LABEL = localize('enableForWorkspaceAction', "Enable (Workspace)"); + static readonly LABEL = localize('enableForWorkspaceAction', "Enable (Workspace)"); constructor( @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @@ -879,7 +879,7 @@ export class EnableForWorkspaceAction extends ExtensionAction { export class EnableGloballyAction extends ExtensionAction { static readonly ID = 'extensions.enableGlobally'; - static LABEL = localize('enableGloballyAction', "Enable"); + static readonly LABEL = localize('enableGloballyAction', "Enable"); constructor( @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @@ -906,7 +906,7 @@ export class EnableGloballyAction extends ExtensionAction { export class DisableForWorkspaceAction extends ExtensionAction { static readonly ID = 'extensions.disableForWorkspace'; - static LABEL = localize('disableForWorkspaceAction', "Disable (Workspace)"); + static readonly LABEL = localize('disableForWorkspaceAction', "Disable (Workspace)"); constructor(readonly runningExtensions: IExtensionDescription[], @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @@ -934,7 +934,7 @@ export class DisableForWorkspaceAction extends ExtensionAction { export class DisableGloballyAction extends ExtensionAction { static readonly ID = 'extensions.disableGlobally'; - static LABEL = localize('disableGloballyAction', "Disable"); + static readonly LABEL = localize('disableGloballyAction', "Disable"); constructor(readonly runningExtensions: IExtensionDescription[], @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @@ -1030,7 +1030,7 @@ export class DisableDropDownAction extends ExtensionEditorDropDownAction { export class CheckForUpdatesAction extends Action { static readonly ID = 'workbench.extensions.action.checkForUpdates'; - static LABEL = localize('checkForUpdates', "Check for Extension Updates"); + static readonly LABEL = localize('checkForUpdates', "Check for Extension Updates"); constructor( id = CheckForUpdatesAction.ID, @@ -1102,7 +1102,7 @@ export class ToggleAutoUpdateAction extends Action { export class EnableAutoUpdateAction extends ToggleAutoUpdateAction { static readonly ID = 'workbench.extensions.action.enableAutoUpdate'; - static LABEL = localize('enableAutoUpdate', "Enable Auto Updating Extensions"); + static readonly LABEL = localize('enableAutoUpdate', "Enable Auto Updating Extensions"); constructor( id = EnableAutoUpdateAction.ID, @@ -1116,7 +1116,7 @@ export class EnableAutoUpdateAction extends ToggleAutoUpdateAction { export class DisableAutoUpdateAction extends ToggleAutoUpdateAction { static readonly ID = 'workbench.extensions.action.disableAutoUpdate'; - static LABEL = localize('disableAutoUpdate', "Disable Auto Updating Extensions"); + static readonly LABEL = localize('disableAutoUpdate', "Disable Auto Updating Extensions"); constructor( id = EnableAutoUpdateAction.ID, @@ -1130,7 +1130,7 @@ export class DisableAutoUpdateAction extends ToggleAutoUpdateAction { export class UpdateAllAction extends Action { static readonly ID = 'workbench.extensions.action.updateAllExtensions'; - static LABEL = localize('updateAll', "Update All Extensions"); + static readonly LABEL = localize('updateAll', "Update All Extensions"); constructor( id = UpdateAllAction.ID, @@ -1440,7 +1440,7 @@ export class InstallExtensionsAction extends OpenExtensionsViewletAction { export class ShowEnabledExtensionsAction extends Action { static readonly ID = 'workbench.extensions.action.showEnabledExtensions'; - static LABEL = localize('showEnabledExtensions', "Show Enabled Extensions"); + static readonly LABEL = localize('showEnabledExtensions', "Show Enabled Extensions"); constructor( id: string, @@ -1463,7 +1463,7 @@ export class ShowEnabledExtensionsAction extends Action { export class ShowInstalledExtensionsAction extends Action { static readonly ID = 'workbench.extensions.action.showInstalledExtensions'; - static LABEL = localize('showInstalledExtensions', "Show Installed Extensions"); + static readonly LABEL = localize('showInstalledExtensions', "Show Installed Extensions"); constructor( id: string, @@ -1486,7 +1486,7 @@ export class ShowInstalledExtensionsAction extends Action { export class ShowDisabledExtensionsAction extends Action { static readonly ID = 'workbench.extensions.action.showDisabledExtensions'; - static LABEL = localize('showDisabledExtensions', "Show Disabled Extensions"); + static readonly LABEL = localize('showDisabledExtensions', "Show Disabled Extensions"); constructor( id: string, @@ -1509,7 +1509,7 @@ export class ShowDisabledExtensionsAction extends Action { export class ClearExtensionsInputAction extends Action { static readonly ID = 'workbench.extensions.action.clearExtensionsInput'; - static LABEL = localize('clearExtensionsInput', "Clear Extensions Input"); + static readonly LABEL = localize('clearExtensionsInput', "Clear Extensions Input"); constructor( id: string, @@ -1540,7 +1540,7 @@ export class ClearExtensionsInputAction extends Action { export class ShowBuiltInExtensionsAction extends Action { static readonly ID = 'workbench.extensions.action.listBuiltInExtensions'; - static LABEL = localize('showBuiltInExtensions', "Show Built-in Extensions"); + static readonly LABEL = localize('showBuiltInExtensions', "Show Built-in Extensions"); constructor( id: string, @@ -1563,7 +1563,7 @@ export class ShowBuiltInExtensionsAction extends Action { export class ShowOutdatedExtensionsAction extends Action { static readonly ID = 'workbench.extensions.action.listOutdatedExtensions'; - static LABEL = localize('showOutdatedExtensions', "Show Outdated Extensions"); + static readonly LABEL = localize('showOutdatedExtensions', "Show Outdated Extensions"); constructor( id: string, @@ -1586,7 +1586,7 @@ export class ShowOutdatedExtensionsAction extends Action { export class ShowPopularExtensionsAction extends Action { static readonly ID = 'workbench.extensions.action.showPopularExtensions'; - static LABEL = localize('showPopularExtensions', "Show Popular Extensions"); + static readonly LABEL = localize('showPopularExtensions', "Show Popular Extensions"); constructor( id: string, @@ -1609,7 +1609,7 @@ export class ShowPopularExtensionsAction extends Action { export class ShowRecommendedExtensionsAction extends Action { static readonly ID = 'workbench.extensions.action.showRecommendedExtensions'; - static LABEL = localize('showRecommendedExtensions', "Show Recommended Extensions"); + static readonly LABEL = localize('showRecommendedExtensions', "Show Recommended Extensions"); constructor( id: string, @@ -1632,7 +1632,7 @@ export class ShowRecommendedExtensionsAction extends Action { export class InstallWorkspaceRecommendedExtensionsAction extends Action { static readonly ID = 'workbench.extensions.action.installWorkspaceRecommendedExtensions'; - static LABEL = localize('installWorkspaceRecommendedExtensions', "Install All Workspace Recommended Extensions"); + static readonly LABEL = localize('installWorkspaceRecommendedExtensions', "Install All Workspace Recommended Extensions"); private _recommendations: IExtensionRecommendation[] = []; get recommendations(): IExtensionRecommendation[] { return this._recommendations; } @@ -1697,7 +1697,7 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action { export class InstallRecommendedExtensionAction extends Action { static readonly ID = 'workbench.extensions.action.installRecommendedExtension'; - static LABEL = localize('installRecommendedExtension', "Install Recommended Extension"); + static readonly LABEL = localize('installRecommendedExtension', "Install Recommended Extension"); private extensionId: string; @@ -1788,7 +1788,7 @@ export class UndoIgnoreExtensionRecommendationAction extends Action { export class ShowRecommendedKeymapExtensionsAction extends Action { static readonly ID = 'workbench.extensions.action.showRecommendedKeymapExtensions'; - static SHORT_LABEL = localize('showRecommendedKeymapExtensionsShort', "Keymaps"); + static readonly SHORT_LABEL = localize('showRecommendedKeymapExtensionsShort', "Keymaps"); constructor( id: string, @@ -1811,7 +1811,7 @@ export class ShowRecommendedKeymapExtensionsAction extends Action { export class ShowLanguageExtensionsAction extends Action { static readonly ID = 'workbench.extensions.action.showLanguageExtensions'; - static SHORT_LABEL = localize('showLanguageExtensionsShort', "Language Extensions"); + static readonly SHORT_LABEL = localize('showLanguageExtensionsShort', "Language Extensions"); constructor( id: string, @@ -1834,7 +1834,7 @@ export class ShowLanguageExtensionsAction extends Action { export class ShowAzureExtensionsAction extends Action { static readonly ID = 'workbench.extensions.action.showAzureExtensions'; - static SHORT_LABEL = localize('showAzureExtensionsShort', "Azure Extensions"); + static readonly SHORT_LABEL = localize('showAzureExtensionsShort', "Azure Extensions"); constructor( id: string, @@ -2176,7 +2176,7 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio export class ConfigureWorkspaceRecommendedExtensionsAction extends AbstractConfigureRecommendedExtensionsAction { static readonly ID = 'workbench.extensions.action.configureWorkspaceRecommendedExtensions'; - static LABEL = localize('configureWorkspaceRecommendedExtensions', "Configure Recommended Extensions (Workspace)"); + static readonly LABEL = localize('configureWorkspaceRecommendedExtensions', "Configure Recommended Extensions (Workspace)"); constructor( @@ -2212,7 +2212,7 @@ export class ConfigureWorkspaceRecommendedExtensionsAction extends AbstractConfi export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends AbstractConfigureRecommendedExtensionsAction { static readonly ID = 'workbench.extensions.action.configureWorkspaceFolderRecommendedExtensions'; - static LABEL = localize('configureWorkspaceFolderRecommendedExtensions', "Configure Recommended Extensions (Workspace Folder)"); + static readonly LABEL = localize('configureWorkspaceFolderRecommendedExtensions', "Configure Recommended Extensions (Workspace Folder)"); constructor( @@ -2685,7 +2685,7 @@ export class SystemDisabledWarningAction extends ExtensionAction { export class DisableAllAction extends Action { static readonly ID = 'workbench.extensions.action.disableAll'; - static LABEL = localize('disableAll', "Disable All Installed Extensions"); + static readonly LABEL = localize('disableAll', "Disable All Installed Extensions"); constructor( @@ -2710,7 +2710,7 @@ export class DisableAllAction extends Action { export class DisableAllWorkspaceAction extends Action { static readonly ID = 'workbench.extensions.action.disableAllWorkspace'; - static LABEL = localize('disableAllWorkspace', "Disable All Installed Extensions for this Workspace"); + static readonly LABEL = localize('disableAllWorkspace', "Disable All Installed Extensions for this Workspace"); constructor( @@ -2737,7 +2737,7 @@ export class DisableAllWorkspaceAction extends Action { export class EnableAllAction extends Action { static readonly ID = 'workbench.extensions.action.enableAll'; - static LABEL = localize('enableAll', "Enable All Extensions"); + static readonly LABEL = localize('enableAll', "Enable All Extensions"); constructor( @@ -2762,7 +2762,7 @@ export class EnableAllAction extends Action { export class EnableAllWorkspaceAction extends Action { static readonly ID = 'workbench.extensions.action.enableAllWorkspace'; - static LABEL = localize('enableAllWorkspace', "Enable All Extensions for this Workspace"); + static readonly LABEL = localize('enableAllWorkspace', "Enable All Extensions for this Workspace"); constructor( @@ -2789,7 +2789,7 @@ export class EnableAllWorkspaceAction extends Action { export class InstallVSIXAction extends Action { static readonly ID = 'workbench.extensions.action.installVSIX'; - static LABEL = localize('installVSIX', "Install from VSIX..."); + static readonly LABEL = localize('installVSIX', "Install from VSIX..."); constructor( id = InstallVSIXAction.ID, @@ -2889,7 +2889,7 @@ export class InstallVSIXAction extends Action { export class ReinstallAction extends Action { static readonly ID = 'workbench.extensions.action.reinstall'; - static LABEL = localize('reinstall', "Reinstall Extension..."); + static readonly LABEL = localize('reinstall', "Reinstall Extension..."); constructor( id: string = ReinstallAction.ID, label: string = ReinstallAction.LABEL, @@ -2956,7 +2956,7 @@ export class ReinstallAction extends Action { export class InstallSpecificVersionOfExtensionAction extends Action { static readonly ID = 'workbench.extensions.action.install.specificVersion'; - static LABEL = localize('install previous version', "Install Specific Version of Extension..."); + static readonly LABEL = localize('install previous version', "Install Specific Version of Extension..."); constructor( id: string = InstallSpecificVersionOfExtensionAction.ID, label: string = InstallSpecificVersionOfExtensionAction.LABEL, diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts index 203c09d4a3..ec5dbe78f6 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts @@ -213,7 +213,7 @@ export class ExtensionsTree extends WorkbenchAsyncDataTree { + this.disposables.add(this.onDidChangeSelection(event => { if (event.browserEvent && event.browserEvent instanceof KeyboardEvent) { extensionsWorkdbenchService.open(event.elements[0].extension, false); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index a008db4673..ff80ea764f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -787,10 +787,10 @@ export class ExtensionsListView extends ViewletPanel { if (count === 0 && this.isBodyVisible()) { if (error) { if (error instanceof ExtensionListViewWarning) { - this.bodyTemplate.messageSeverityIcon.className = SeverityIcon.className(Severity.Warning); + this.bodyTemplate.messageSeverityIcon.className = `codicon ${SeverityIcon.className(Severity.Warning)}`; this.bodyTemplate.messageBox.textContent = getErrorMessage(error); } else { - this.bodyTemplate.messageSeverityIcon.className = SeverityIcon.className(Severity.Error); + this.bodyTemplate.messageSeverityIcon.className = `codicon ${SeverityIcon.className(Severity.Error)}`; this.bodyTemplate.messageBox.textContent = localize('error', "Error while loading extensions. {0}", getErrorMessage(error)); } } else { @@ -961,7 +961,7 @@ export class ServerExtensionsView extends ExtensionsListView { getActions(): IAction[] { if (this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManagementServerService.localExtensionManagementServer === this.server) { const installLocalExtensionsInRemoteAction = this._register(this.instantiationService.createInstance(InstallLocalExtensionsInRemoteAction)); - installLocalExtensionsInRemoteAction.class = 'octicon octicon-cloud-download'; + installLocalExtensionsInRemoteAction.class = 'codicon codicon-cloud-download'; return [installLocalExtensionsInRemoteAction]; } return []; @@ -1060,11 +1060,11 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView { getActions(): IAction[] { if (!this.installAllAction) { this.installAllAction = this._register(this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, InstallWorkspaceRecommendedExtensionsAction.LABEL, [])); - this.installAllAction.class = 'octicon octicon-cloud-download'; + this.installAllAction.class = 'codicon codicon-cloud-download'; } const configureWorkspaceFolderAction = this._register(this.instantiationService.createInstance(ConfigureWorkspaceFolderRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL)); - configureWorkspaceFolderAction.class = 'octicon octicon-pencil'; + configureWorkspaceFolderAction.class = 'codicon codicon-pencil'; return [this.installAllAction, configureWorkspaceFolderAction]; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index 8b41f72055..b90e7b77e6 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -81,7 +81,7 @@ export class InstallCountWidget extends ExtensionWidget { installLabel = installCount.toLocaleString(platform.locale); } - append(this.container, $('span.octicon.octicon-cloud-download')); + append(this.container, $('span.codicon.codicon-cloud-download')); const count = append(this.container, $('span.count')); count.textContent = installLabel; } @@ -224,7 +224,7 @@ export class RecommendationWidget extends ExtensionWidget { if (extRecommendations[this.extension.identifier.id.toLowerCase()]) { this.element = append(this.parent, $('div.bookmark')); const recommendation = append(this.element, $('.recommendation')); - append(recommendation, $('span.octicon.octicon-star')); + append(recommendation, $('span.codicon.codicon-star')); const applyBookmarkStyle = (theme: ITheme) => { const bgColor = theme.getColor(extensionButtonProminentBackground); const fgColor = theme.getColor(extensionButtonProminentForeground); @@ -290,7 +290,7 @@ class RemoteBadge extends Disposable { } private render(): void { - append(this.element, $('span.octicon.octicon-remote')); + append(this.element, $('span.codicon.codicon-remote')); const applyBadgeStyle = () => { if (!this.element) { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 32c9d08940..aeeb7e38c5 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -15,7 +15,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; // {{SQL CARBON EDIT}} import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, - InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, IExtensionIdentifier + InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, IExtensionIdentifier, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, getMaliciousExtensionsSet, groupByExtension, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -25,7 +25,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { URI } from 'vs/base/common/uri'; import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey } from 'vs/workbench/contrib/extensions/common/extensions'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; +import { IURLService, IURLHandler, IOpenURLOptions } from 'vs/platform/url/common/url'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ILogService } from 'vs/platform/log/common/log'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; @@ -323,8 +323,8 @@ ${this.description} class Extensions extends Disposable { - private readonly _onChange: Emitter = new Emitter(); - get onChange(): Event { return this._onChange.event; } + private readonly _onChange: Emitter<{ extension: Extension, operation?: InstallOperation } | undefined> = this._register(new Emitter<{ extension: Extension, operation?: InstallOperation } | undefined>()); + get onChange(): Event<{ extension: Extension, operation?: InstallOperation } | undefined> { return this._onChange.event; } private installing: Extension[] = []; private uninstalling: Extension[] = []; @@ -385,7 +385,7 @@ class Extensions extends Disposable { const local = extension.local.metadata ? extension.local : await this.server.extensionManagementService.updateMetadata(extension.local, { id: compatible.identifier.uuid, publisherDisplayName: compatible.publisherDisplayName, publisherId: compatible.publisherId }); extension.local = local; extension.gallery = compatible; - this._onChange.fire(extension); + this._onChange.fire({ extension }); return true; } return false; @@ -412,7 +412,7 @@ class Extensions extends Disposable { const extension = this.installed.filter(e => areSameExtensions(e.identifier, gallery.identifier))[0] || this.instantiationService.createInstance(Extension, this.stateProvider, this.server, undefined, gallery); this.installing.push(extension); - this._onChange.fire(extension); + this._onChange.fire({ extension }); } } @@ -436,9 +436,10 @@ class Extensions extends Disposable { if (!extension.gallery) { extension.gallery = gallery; } + extension.enablementState = this.extensionEnablementService.getEnablementState(local); } } - this._onChange.fire(error ? undefined : extension); + this._onChange.fire(error || !extension ? undefined : { extension, operation: event.operation }); } private onUninstallExtension(identifier: IExtensionIdentifier): void { @@ -446,7 +447,7 @@ class Extensions extends Disposable { if (extension) { const uninstalling = this.uninstalling.filter(e => areSameExtensions(e.identifier, identifier))[0] || extension; this.uninstalling = [uninstalling, ...this.uninstalling.filter(e => !areSameExtensions(e.identifier, identifier))]; - this._onChange.fire(uninstalling); + this._onChange.fire(uninstalling ? { extension: uninstalling } : undefined); } } @@ -457,7 +458,7 @@ class Extensions extends Disposable { const uninstalling = this.uninstalling.filter(e => areSameExtensions(e.identifier, identifier))[0]; this.uninstalling = this.uninstalling.filter(e => !areSameExtensions(e.identifier, identifier)); if (uninstalling) { - this._onChange.fire(uninstalling); + this._onChange.fire({ extension: uninstalling }); } } @@ -468,7 +469,7 @@ class Extensions extends Disposable { const enablementState = this.extensionEnablementService.getEnablementState(extension.local); if (enablementState !== extension.enablementState) { (extension as Extension).enablementState = enablementState; - this._onChange.fire(extension as Extension); + this._onChange.fire({ extension: extension as Extension }); } } } @@ -522,11 +523,13 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension super(); if (this.extensionManagementServerService.localExtensionManagementServer) { this.localExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.localExtensionManagementServer, ext => this.getExtensionState(ext))); - this._register(this.localExtensions.onChange(e => this._onChange.fire(e))); + this._register(this.localExtensions.onChange(e => this._onChange.fire(e ? e.extension : undefined))); + this._register(Event.filter(this.localExtensions.onChange, e => !!e && e.operation === InstallOperation.Install)(e => this.onDidInstallExtension(e!.extension))); } if (this.extensionManagementServerService.remoteExtensionManagementServer) { this.remoteExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.remoteExtensionManagementServer, ext => this.getExtensionState(ext))); - this._register(this.remoteExtensions.onChange(e => this._onChange.fire(e))); + this._register(this.remoteExtensions.onChange(e => this._onChange.fire(e ? e.extension : undefined))); + this._register(Event.filter(this.remoteExtensions.onChange, e => !!e && e.operation === InstallOperation.Install)(e => this.onDidInstallExtension(e!.extension))); } this.syncDelayer = new ThrottledDelayer(ExtensionsWorkbenchService.SyncPeriod); @@ -807,7 +810,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension // Prompt the user about the error detail. try { const { identifier } = await this.extensionService.install(extension); - this.checkAndEnableDisabledDependencies(identifier); return this.local.filter(local => areSameExtensions(local.identifier, identifier))[0]; } catch (error) { this.notificationService.error(error); @@ -840,12 +842,12 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension // {{SQL CARBON EDIT}} remove extensionservice install from gallery if (extensionPolicy === ExtensionsPolicy.allowMicrosoft) { if (extension.publisherDisplayName === 'Microsoft') { - await this.downloadOrBrowse(extension).then(() => this.checkAndEnableDisabledDependencies(gallery.identifier)); + await this.downloadOrBrowse(extension); } else { return Promise.resolve(null); } } - await this.downloadOrBrowse(extension).then(() => this.checkAndEnableDisabledDependencies(gallery.identifier)); + await this.downloadOrBrowse(extension); return this.local.filter(local => areSameExtensions(local.identifier, gallery.identifier))[0]; }, gallery.displayName); } @@ -942,15 +944,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } } - private checkAndEnableDisabledDependencies(extensionIdentifier: IExtensionIdentifier): Promise { - const extension = this.local.filter(e => (e.local || e.gallery) && areSameExtensions(extensionIdentifier, e.identifier))[0]; - if (extension) { - const disabledDepencies = this.getExtensionsRecursively([extension], this.local, EnablementState.EnabledGlobally, { dependencies: true, pack: false }); - if (disabledDepencies.length) { - return this.setEnablement(disabledDepencies, EnablementState.EnabledGlobally); - } - } - return Promise.resolve(); + private onDidInstallExtension(extension: IExtension): void { + this.setEnablement(extension, EnablementState.EnabledGlobally); } private promptAndSetEnablement(extensions: IExtension[], enablementState: EnablementState): Promise { @@ -1103,7 +1098,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension this.notificationService.error(err); } - handleURL(uri: URI): Promise { + handleURL(uri: URI, options?: IOpenURLOptions): Promise { if (!/^extension/.test(uri.path)) { return Promise.resolve(false); } diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index 4a5ae96fa1..aed18ad134 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -46,7 +46,7 @@ justify-content: center; } -.extension-editor > .header > .icon-container .extension-remote-badge .octicon { +.extension-editor > .header > .icon-container .extension-remote-badge .codicon { font-size: 28px; } diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css index 36be0b6f78..7e0a854e3c 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css @@ -8,9 +8,9 @@ } .extensions-viewlet > .header { - height: 38px; + height: 41px; box-sizing: border-box; - padding: 5px 9px 5px 16px; + padding: 5px 12px 6px 16px; } .extensions-viewlet > .header > .search-box { @@ -31,7 +31,7 @@ margin-right: 4px; } -.extensions-viewlet > .extensions .extension-view-header .monaco-action-bar .action-item > .action-label.icon.octicon { +.extensions-viewlet > .extensions .extension-view-header .monaco-action-bar .action-item > .action-label.icon.codicon { vertical-align: middle; line-height: 22px; } @@ -47,7 +47,7 @@ } .extensions-viewlet > .extensions .panel-header { - padding-right: 6px; + padding-right: 12px; } .extensions-viewlet > .extensions .panel-header > .title { @@ -84,10 +84,11 @@ box-sizing: border-box; } -.extensions-viewlet > .extensions .monaco-list-row > .bookmark > .recommendation > .octicon { +.extensions-viewlet > .extensions .monaco-list-row > .bookmark > .recommendation > .codicon { position: absolute; top: 1px; left: 1px; + color: inherit; font-size: 90%; } @@ -146,7 +147,7 @@ justify-content: center; } -.extensions-viewlet > .extensions .monaco-list-row > .extension > .details > .header-container > .header .extension-remote-badge > .octicon { +.extensions-viewlet > .extensions .monaco-list-row > .extension > .details > .header-container > .header .extension-remote-badge > .codicon { font-size: 12px; } @@ -200,7 +201,7 @@ margin: 0 6px; } -.extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count > .octicon { +.extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count > .codicon { font-size: 120%; margin-right: 2px; } diff --git a/src/vs/workbench/contrib/extensions/browser/media/language-icon.svg b/src/vs/workbench/contrib/extensions/browser/media/language-icon.svg old mode 100755 new mode 100644 diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts index e4c21f47d2..39cfaeed38 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts @@ -107,12 +107,12 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio } } - public startProfiling(): Promise | null { + public async startProfiling(): Promise { if (this._state !== ProfileSessionState.None) { return null; } - const inspectPort = this._extensionService.getInspectPort(); + const inspectPort = await this._extensionService.getInspectPort(false); if (!inspectPort) { return this._dialogService.confirm({ type: 'info', diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts index 74092653ca..af48e9f3f0 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts @@ -14,7 +14,7 @@ import { Schemas } from 'vs/base/common/network'; export class OpenExtensionsFolderAction extends Action { static readonly ID = 'workbench.extensions.action.openExtensionsFolder'; - static LABEL = localize('openExtensionsFolder', "Open Extensions Folder"); + static readonly LABEL = localize('openExtensionsFolder', "Open Extensions Folder"); constructor( id: string, diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts index b2b9ea8241..46053f662c 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts @@ -43,7 +43,7 @@ export class ExtensionsAutoProfiler extends Disposable implements IWorkbenchCont private async _onDidChangeResponsiveChange(event: IResponsiveStateChangeEvent): Promise { - const port = this._extensionService.getInspectPort(); + const port = await this._extensionService.getInspectPort(true); if (!port) { return; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts index 0a2e73cd19..84d8459b0a 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -38,7 +38,7 @@ import { randomPort } from 'vs/base/node/ports'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ILabelService } from 'vs/platform/label/common/label'; -import { renderOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; +import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; import { ExtensionIdentifier, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { SlowExtensionAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions'; @@ -364,31 +364,31 @@ export class RuntimeExtensionsEditor extends BaseEditor { if (this._extensionHostProfileService.getUnresponsiveProfile(element.description.identifier)) { const el = $('span'); - el.innerHTML = renderOcticons(` $(alert) Unresponsive`); + el.innerHTML = renderCodicons(` $(alert) Unresponsive`); el.title = nls.localize('unresponsive.title', "Extension has caused the extension host to freeze."); data.msgContainer.appendChild(el); } if (isNonEmptyArray(element.status.runtimeErrors)) { const el = $('span'); - el.innerHTML = renderOcticons(`$(bug) ${nls.localize('errors', "{0} uncaught errors", element.status.runtimeErrors.length)}`); + el.innerHTML = renderCodicons(`$(bug) ${nls.localize('errors', "{0} uncaught errors", element.status.runtimeErrors.length)}`); data.msgContainer.appendChild(el); } if (element.status.messages && element.status.messages.length > 0) { const el = $('span'); - el.innerHTML = renderOcticons(`$(alert) ${element.status.messages[0].message}`); + el.innerHTML = renderCodicons(`$(alert) ${element.status.messages[0].message}`); data.msgContainer.appendChild(el); } if (element.description.extensionLocation.scheme !== 'file') { const el = $('span'); - el.innerHTML = renderOcticons(`$(remote) ${element.description.extensionLocation.authority}`); + el.innerHTML = renderCodicons(`$(remote) ${element.description.extensionLocation.authority}`); data.msgContainer.appendChild(el); const hostLabel = this._labelService.getHostLabel(REMOTE_HOST_SCHEME, this._environmentService.configuration.remoteAuthority); if (hostLabel) { - el.innerHTML = renderOcticons(`$(remote) ${hostLabel}`); + el.innerHTML = renderCodicons(`$(remote) ${hostLabel}`); } } @@ -459,7 +459,7 @@ export class RuntimeExtensionsEditor extends BaseEditor { export class ShowRuntimeExtensionsAction extends Action { static readonly ID = 'workbench.action.showRuntimeExtensions'; - static LABEL = nls.localize('showRuntimeExtensions', "Show Running Extensions"); + static readonly LABEL = nls.localize('showRuntimeExtensions', "Show Running Extensions"); constructor( id: string, label: string, @@ -477,7 +477,7 @@ export class ShowRuntimeExtensionsAction extends Action { export class ReportExtensionIssueAction extends Action { private static readonly _id = 'workbench.extensions.action.reportExtensionIssue'; - private static _label = nls.localize('reportExtensionIssue', "Report Issue"); + private static readonly _label = nls.localize('reportExtensionIssue', "Report Issue"); private readonly _url: string; @@ -533,8 +533,8 @@ export class ReportExtensionIssueAction extends Action { export class DebugExtensionHostAction extends Action { static readonly ID = 'workbench.extensions.action.debugExtensionHost'; - static LABEL = nls.localize('debugExtensionHost', "Start Debugging Extension Host"); - static CSS_CLASS = 'debug-extension-host'; + static readonly LABEL = nls.localize('debugExtensionHost', "Start Debugging Extension Host"); + static readonly CSS_CLASS = 'debug-extension-host'; constructor( @IDebugService private readonly _debugService: IDebugService, @@ -547,7 +547,7 @@ export class DebugExtensionHostAction extends Action { async run(): Promise { - const inspectPort = this._extensionService.getInspectPort(); + const inspectPort = await this._extensionService.getInspectPort(false); if (!inspectPort) { const res = await this._dialogService.confirm({ type: 'info', @@ -557,8 +557,10 @@ export class DebugExtensionHostAction extends Action { secondaryButton: nls.localize('cancel', "Cancel") }); if (res.confirmed) { - this._electronService.relaunch({ addArgs: [`--inspect-extensions=${randomPort()}`] }); + await this._electronService.relaunch({ addArgs: [`--inspect-extensions=${randomPort()}`] }); } + + return; } return this._debugService.startDebugging(undefined, { @@ -572,7 +574,7 @@ export class DebugExtensionHostAction extends Action { export class StartExtensionHostProfileAction extends Action { static readonly ID = 'workbench.extensions.action.extensionHostProfile'; - static LABEL = nls.localize('extensionHostProfileStart', "Start Extension Host Profile"); + static readonly LABEL = nls.localize('extensionHostProfileStart', "Start Extension Host Profile"); constructor( id: string = StartExtensionHostProfileAction.ID, label: string = StartExtensionHostProfileAction.LABEL, @@ -589,7 +591,7 @@ export class StartExtensionHostProfileAction extends Action { export class StopExtensionHostProfileAction extends Action { static readonly ID = 'workbench.extensions.action.stopExtensionHostProfile'; - static LABEL = nls.localize('stopExtensionHostProfileStart', "Stop Extension Host Profile"); + static readonly LABEL = nls.localize('stopExtensionHostProfileStart', "Stop Extension Host Profile"); constructor( id: string = StartExtensionHostProfileAction.ID, label: string = StartExtensionHostProfileAction.LABEL, @@ -606,7 +608,7 @@ export class StopExtensionHostProfileAction extends Action { export class SaveExtensionHostProfileAction extends Action { - static LABEL = nls.localize('saveExtensionHostProfile', "Save Extension Host Profile"); + static readonly LABEL = nls.localize('saveExtensionHostProfile', "Save Extension Host Profile"); static readonly ID = 'workbench.extensions.action.saveExtensionHostProfile'; constructor( @@ -636,7 +638,7 @@ export class SaveExtensionHostProfileAction extends Action { }] }); - if (!picked || !picked.filePath) { + if (!picked || !picked.filePath || picked.canceled) { return; } diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts index a9a9cba802..965d9a1493 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -305,7 +305,6 @@ suite.skip('ExtensionsTipsService Test', () => { // {{SQL CARBON EDIT}} skip sui function testNoPromptOrRecommendationsForValidRecommendations(recommendations: string[]) { return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => { testObject = instantiationService.createInstance(ExtensionTipsService); - assert.equal(!testObject.loadWorkspaceConfigPromise, true); assert.ok(!prompted); return testObject.getWorkspaceRecommendations().then(() => { 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 b0b523db5f..90c0ce90dd 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 @@ -942,6 +942,46 @@ suite('ExtensionsWorkbenchServiceTest', () => { }); }); + test('test installing an extension re-eanbles it when disabled globally', async () => { + testObject = await aWorkbenchService(); + const local = aLocalExtension('pub.a'); + await instantiationService.get(IExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally); + didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Install }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + const actual = await testObject.queryLocal(); + assert.equal(actual[0].enablementState, EnablementState.EnabledGlobally); + }); + + test('test updating an extension does not re-eanbles it when disabled globally', async () => { + testObject = await aWorkbenchService(); + const local = aLocalExtension('pub.a'); + await instantiationService.get(IExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally); + didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Update }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + const actual = await testObject.queryLocal(); + assert.equal(actual[0].enablementState, EnablementState.DisabledGlobally); + }); + + test('test installing an extension re-eanbles it when workspace disabled', async () => { + testObject = await aWorkbenchService(); + const local = aLocalExtension('pub.a'); + await instantiationService.get(IExtensionEnablementService).setEnablement([local], EnablementState.DisabledWorkspace); + didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Install }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + const actual = await testObject.queryLocal(); + assert.equal(actual[0].enablementState, EnablementState.EnabledGlobally); + }); + + test('test updating an extension does not re-eanbles it when workspace disabled', async () => { + testObject = await aWorkbenchService(); + const local = aLocalExtension('pub.a'); + await instantiationService.get(IExtensionEnablementService).setEnablement([local], EnablementState.DisabledWorkspace); + didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Update }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + const actual = await testObject.queryLocal(); + assert.equal(actual[0].enablementState, EnablementState.DisabledWorkspace); + }); + async function aWorkbenchService(): Promise { const workbenchService: ExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); await workbenchService.queryLocal(); diff --git a/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts b/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts index af4cb39bbd..d9bffbf186 100644 --- a/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts +++ b/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts @@ -130,7 +130,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: OPEN_NATIVE_CONSOLE_COMMAND_ID, - title: { value: nls.localize('globalConsoleAction', "Open New Terminal"), original: 'Open New Terminal' } + title: { value: nls.localize('globalConsoleAction', "Open New External Terminal"), original: 'Open New External Terminal' } } }); diff --git a/src/vs/workbench/contrib/feedback/browser/feedback.ts b/src/vs/workbench/contrib/feedback/browser/feedback.ts index a22ee81430..a2a6f01f69 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedback.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedback.ts @@ -142,7 +142,7 @@ export class FeedbackDropdown extends Dropdown { })); disposables.add(dom.addDisposableListener(closeBtn, dom.EventType.MOUSE_OUT, () => { - closeBtn.style.backgroundColor = null; + closeBtn.style.backgroundColor = ''; })); this.invoke(closeBtn, disposables, () => this.hide()); @@ -276,17 +276,17 @@ export class FeedbackDropdown extends Dropdown { disposables.add(attachStylerCallback(this.themeService, { widgetShadow, editorWidgetBackground, editorWidgetForeground, inputBackground, inputForeground, inputBorder, editorBackground, contrastBorder }, colors => { if (this.feedbackForm) { - this.feedbackForm.style.backgroundColor = colors.editorWidgetBackground ? colors.editorWidgetBackground.toString() : null; + this.feedbackForm.style.backgroundColor = colors.editorWidgetBackground ? colors.editorWidgetBackground.toString() : ''; this.feedbackForm.style.color = colors.editorWidgetForeground ? colors.editorWidgetForeground.toString() : null; - this.feedbackForm.style.boxShadow = colors.widgetShadow ? `0 0 8px ${colors.widgetShadow}` : null; + this.feedbackForm.style.boxShadow = colors.widgetShadow ? `0 0 8px ${colors.widgetShadow}` : ''; } if (this.feedbackDescriptionInput) { - this.feedbackDescriptionInput.style.backgroundColor = colors.inputBackground ? colors.inputBackground.toString() : null; + this.feedbackDescriptionInput.style.backgroundColor = colors.inputBackground ? colors.inputBackground.toString() : ''; this.feedbackDescriptionInput.style.color = colors.inputForeground ? colors.inputForeground.toString() : null; this.feedbackDescriptionInput.style.border = `1px solid ${colors.inputBorder || 'transparent'}`; } - contactUsContainer.style.backgroundColor = colors.editorBackground ? colors.editorBackground.toString() : null; + contactUsContainer.style.backgroundColor = colors.editorBackground ? colors.editorBackground.toString() : ''; contactUsContainer.style.border = `1px solid ${colors.contrastBorder || 'transparent'}`; })); diff --git a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts index 1417ea1a74..213a39485d 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts @@ -71,7 +71,11 @@ export class FeedbackStatusbarConribution extends Disposable implements IWorkben if (!this.dropdown) { const statusContainr = document.getElementById('status.feedback'); if (statusContainr) { - this.dropdown = this._register(this.instantiationService.createInstance(FeedbackDropdown, statusContainr.getElementsByClassName('octicon').item(0), { + const icon = statusContainr.getElementsByClassName('codicon').item(0) as HTMLElement | null; + if (!icon) { + throw new Error('Could not find icon'); + } + this.dropdown = this._register(this.instantiationService.createInstance(FeedbackDropdown, icon, { contextViewProvider: this.contextViewService, feedbackService: this.instantiationService.createInstance(TwitterFeedbackService), onFeedbackVisibilityChange: visible => this.entry!.update(this.getStatusEntry(visible)) diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index ba56563118..c949dbad2d 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -78,7 +78,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut } private onConfigurationUpdated(configuration: IWorkbenchEditorConfiguration): void { - if (configuration.workbench && configuration.workbench.editor && typeof configuration.workbench.editor.closeOnFileDelete === 'boolean') { + if (typeof configuration.workbench?.editor?.closeOnFileDelete === 'boolean') { this.closeOnFileDelete = configuration.workbench.editor.closeOnFileDelete; } else { this.closeOnFileDelete = false; // default @@ -273,7 +273,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut const editors = this.editorService.visibleControls; for (const editor of editors) { - if (editor && editor.input && editor.group === group) { + if (editor?.input && editor.group === group) { const editorResource = editor.input.getResource(); if (editorResource && resource.toString() === editorResource.toString()) { const control = editor.getControl(); @@ -328,7 +328,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut let isBinaryEditor = false; if (editor instanceof SideBySideEditor) { const masterEditor = editor.getMasterEditor(); - isBinaryEditor = !!masterEditor && masterEditor.getId() === BINARY_FILE_EDITOR_ID; + isBinaryEditor = masterEditor?.getId() === BINARY_FILE_EDITOR_ID; } else { isBinaryEditor = editor.getId() === BINARY_FILE_EDITOR_ID; } diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index 1b7456c472..84641c148b 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { isFunction } from 'vs/base/common/types'; +import { isFunction, assertIsDefined } from 'vs/base/common/types'; import { isValidBasename } from 'vs/base/common/extpath'; import { basename } from 'vs/base/common/resources'; import { Action } from 'vs/base/common/actions'; @@ -68,7 +68,7 @@ export class TextFileEditor extends BaseTextEditor { private onFilesChanged(e: FileChangesEvent): void { const deleted = e.getDeleted(); - if (deleted && deleted.length) { + if (deleted?.length) { this.clearTextEditorViewState(deleted.map(d => d.resource)); } } @@ -118,7 +118,8 @@ export class TextFileEditor extends BaseTextEditor { setOptions(options: EditorOptions | undefined): void { const textOptions = options as TextEditorOptions; if (textOptions && isFunction(textOptions.apply)) { - textOptions.apply(this.getControl(), ScrollType.Smooth); + const textEditor = assertIsDefined(this.getControl()); + textOptions.apply(textEditor, ScrollType.Smooth); } } @@ -147,7 +148,7 @@ export class TextFileEditor extends BaseTextEditor { const textFileModel = resolvedModel; // Editor - const textEditor = this.getControl(); + const textEditor = assertIsDefined(this.getControl()); textEditor.setModel(textFileModel.textEditorModel); // Always restore View State if any associated @@ -241,7 +242,7 @@ export class TextFileEditor extends BaseTextEditor { protected getAriaLabel(): string { const input = this.input; - const inputName = input && input.getName(); + const inputName = input?.getName(); let ariaLabel: string; if (inputName) { @@ -259,7 +260,10 @@ export class TextFileEditor extends BaseTextEditor { this.doSaveOrClearTextEditorViewState(this.input); // Clear Model - this.getControl().setModel(null); + const textEditor = this.getControl(); + if (textEditor) { + textEditor.setModel(null); + } // Pass to super super.clearInput(); diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index dce2a76c68..9621d38615 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -241,7 +241,7 @@ export class ExplorerViewlet extends ViewContainerViewlet { focus(): void { const explorerView = this.getView(ExplorerView.ID); - if (explorerView && explorerView.isExpanded()) { + if (explorerView?.isExpanded()) { explorerView.focus(); } else { super.focus(); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 6ba4fac8f5..648dde9b25 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -39,7 +39,7 @@ import { Schemas } from 'vs/base/common/network'; import { IDialogService, IConfirmationResult, getConfirmMessage } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { Constants } from 'vs/editor/common/core/uint'; +import { Constants } from 'vs/base/common/uint'; import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { coalesce } from 'vs/base/common/arrays'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; @@ -149,8 +149,8 @@ export class GlobalNewUntitledPlainFileAction extends Action { /* Create new file from anywhere: Open untitled */ export class GlobalNewUntitledFileAction extends Action { - public static readonly ID = 'workbench.action.files.newUntitledFile'; - public static readonly LABEL = nls.localize('newUntitledFile', "New Untitled File"); + static readonly ID = 'workbench.action.files.newUntitledFile'; + static readonly LABEL = nls.localize('newUntitledFile', "New Untitled File"); constructor( id: string, @@ -162,13 +162,12 @@ export class GlobalNewUntitledFileAction extends Action { super(id, label); } - public run(): Promise { - // {{SQL CARBON EDIT}} - return this.instantiationService.invokeFunction(openNewQuery); + run(): Promise { + return this.instantiationService.invokeFunction(openNewQuery); // {{SQL CARBON EDIT}} } } -function deleteFiles(textFileService: ITextFileService, dialogService: IDialogService, configurationService: IConfigurationService, fileService: IFileService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise { +async function deleteFiles(textFileService: ITextFileService, dialogService: IDialogService, configurationService: IConfigurationService, fileService: IFileService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise { let primaryButton: string; if (useTrash) { primaryButton = isWindows ? nls.localize('deleteButtonLabelRecycleBin', "&&Move to Recycle Bin") : nls.localize({ key: 'deleteButtonLabelTrash', comment: ['&& denotes a mnemonic'] }, "&&Move to Trash"); @@ -179,7 +178,7 @@ function deleteFiles(textFileService: ITextFileService, dialogService: IDialogSe const distinctElements = resources.distinctParents(elements, e => e.resource); // Handle dirty - let confirmDirtyPromise: Promise = Promise.resolve(true); + let confirmed = true; const dirty = textFileService.getDirty().filter(d => distinctElements.some(e => resources.isEqualOrParent(d, e.resource))); if (dirty.length) { let message: string; @@ -195,114 +194,112 @@ function deleteFiles(textFileService: ITextFileService, dialogService: IDialogSe message = nls.localize('dirtyMessageFileDelete', "You are deleting a file with unsaved changes. Do you want to continue?"); } - confirmDirtyPromise = dialogService.confirm({ + const response = await dialogService.confirm({ message, type: 'warning', detail: nls.localize('dirtyWarning', "Your changes will be lost if you don't save them."), primaryButton - }).then(res => { - if (!res.confirmed) { - return false; - } - - skipConfirm = true; // since we already asked for confirmation - return textFileService.revertAll(dirty).then(() => true); }); + + if (!response.confirmed) { + confirmed = false; + } else { + skipConfirm = true; + await textFileService.revertAll(dirty); + } } // Check if file is dirty in editor and save it to avoid data loss - return confirmDirtyPromise.then(confirmed => { - if (!confirmed) { - return undefined; + if (!confirmed) { + return; + } + + let confirmDeletePromise: Promise; + + // Check if we need to ask for confirmation at all + if (skipConfirm || (useTrash && configurationService.getValue(CONFIRM_DELETE_SETTING_KEY) === false)) { + confirmDeletePromise = Promise.resolve({ confirmed: true }); + } + + // Confirm for moving to trash + else if (useTrash) { + const message = getMoveToTrashMessage(distinctElements); + + confirmDeletePromise = dialogService.confirm({ + message, + detail: isWindows ? nls.localize('undoBin', "You can restore from the Recycle Bin.") : nls.localize('undoTrash', "You can restore from the Trash."), + primaryButton, + checkbox: { + label: nls.localize('doNotAskAgain', "Do not ask me again") + }, + type: 'question' + }); + } + + // Confirm for deleting permanently + else { + const message = getDeleteMessage(distinctElements); + confirmDeletePromise = dialogService.confirm({ + message, + detail: nls.localize('irreversible', "This action is irreversible!"), + primaryButton, + type: 'warning' + }); + } + + return confirmDeletePromise.then(confirmation => { + + // Check for confirmation checkbox + let updateConfirmSettingsPromise: Promise = Promise.resolve(undefined); + if (confirmation.confirmed && confirmation.checkboxChecked === true) { + updateConfirmSettingsPromise = configurationService.updateValue(CONFIRM_DELETE_SETTING_KEY, false, ConfigurationTarget.USER); } - let confirmDeletePromise: Promise; + return updateConfirmSettingsPromise.then(() => { - // Check if we need to ask for confirmation at all - if (skipConfirm || (useTrash && configurationService.getValue(CONFIRM_DELETE_SETTING_KEY) === false)) { - confirmDeletePromise = Promise.resolve({ confirmed: true }); - } - - // Confirm for moving to trash - else if (useTrash) { - const message = getMoveToTrashMessage(distinctElements); - - confirmDeletePromise = dialogService.confirm({ - message, - detail: isWindows ? nls.localize('undoBin', "You can restore from the Recycle Bin.") : nls.localize('undoTrash', "You can restore from the Trash."), - primaryButton, - checkbox: { - label: nls.localize('doNotAskAgain', "Do not ask me again") - }, - type: 'question' - }); - } - - // Confirm for deleting permanently - else { - const message = getDeleteMessage(distinctElements); - confirmDeletePromise = dialogService.confirm({ - message, - detail: nls.localize('irreversible', "This action is irreversible!"), - primaryButton, - type: 'warning' - }); - } - - return confirmDeletePromise.then(confirmation => { - - // Check for confirmation checkbox - let updateConfirmSettingsPromise: Promise = Promise.resolve(undefined); - if (confirmation.confirmed && confirmation.checkboxChecked === true) { - updateConfirmSettingsPromise = configurationService.updateValue(CONFIRM_DELETE_SETTING_KEY, false, ConfigurationTarget.USER); + // Check for confirmation + if (!confirmation.confirmed) { + return Promise.resolve(undefined); } - return updateConfirmSettingsPromise.then(() => { + // Call function + const servicePromise = Promise.all(distinctElements.map(e => fileService.del(e.resource, { useTrash: useTrash, recursive: true }))) + .then(undefined, (error: any) => { + // Handle error to delete file(s) from a modal confirmation dialog + let errorMessage: string; + let detailMessage: string | undefined; + let primaryButton: string; + if (useTrash) { + errorMessage = isWindows ? nls.localize('binFailed', "Failed to delete using the Recycle Bin. Do you want to permanently delete instead?") : nls.localize('trashFailed', "Failed to delete using the Trash. Do you want to permanently delete instead?"); + detailMessage = nls.localize('irreversible', "This action is irreversible!"); + primaryButton = nls.localize({ key: 'deletePermanentlyButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Delete Permanently"); + } else { + errorMessage = toErrorMessage(error, false); + primaryButton = nls.localize({ key: 'retryButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Retry"); + } - // Check for confirmation - if (!confirmation.confirmed) { - return Promise.resolve(undefined); - } + return dialogService.confirm({ + message: errorMessage, + detail: detailMessage, + type: 'warning', + primaryButton + }).then(res => { - // Call function - const servicePromise = Promise.all(distinctElements.map(e => fileService.del(e.resource, { useTrash: useTrash, recursive: true }))) - .then(undefined, (error: any) => { - // Handle error to delete file(s) from a modal confirmation dialog - let errorMessage: string; - let detailMessage: string | undefined; - let primaryButton: string; - if (useTrash) { - errorMessage = isWindows ? nls.localize('binFailed', "Failed to delete using the Recycle Bin. Do you want to permanently delete instead?") : nls.localize('trashFailed', "Failed to delete using the Trash. Do you want to permanently delete instead?"); - detailMessage = nls.localize('irreversible', "This action is irreversible!"); - primaryButton = nls.localize({ key: 'deletePermanentlyButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Delete Permanently"); - } else { - errorMessage = toErrorMessage(error, false); - primaryButton = nls.localize({ key: 'retryButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Retry"); - } - - return dialogService.confirm({ - message: errorMessage, - detail: detailMessage, - type: 'warning', - primaryButton - }).then(res => { - - if (res.confirmed) { - if (useTrash) { - useTrash = false; // Delete Permanently - } - - skipConfirm = true; - - return deleteFiles(textFileService, dialogService, configurationService, fileService, elements, useTrash, skipConfirm); + if (res.confirmed) { + if (useTrash) { + useTrash = false; // Delete Permanently } - return Promise.resolve(); - }); - }); + skipConfirm = true; - return servicePromise; - }); + return deleteFiles(textFileService, dialogService, configurationService, fileService, elements, useTrash, skipConfirm); + } + + return Promise.resolve(); + }); + }); + + return servicePromise; }); }); } @@ -467,8 +464,8 @@ export function incrementFileName(name: string, isFolder: boolean, incrementalNa // Global Compare with export class GlobalCompareResourcesAction extends Action { - public static readonly ID = 'workbench.files.action.compareFileWith'; - public static readonly LABEL = nls.localize('globalCompareFile', "Compare Active File With..."); + static readonly ID = 'workbench.files.action.compareFileWith'; + static readonly LABEL = nls.localize('globalCompareFile', "Compare Active File With..."); constructor( id: string, @@ -480,7 +477,7 @@ export class GlobalCompareResourcesAction extends Action { super(id, label); } - public run(): Promise { + async run(): Promise { const activeInput = this.editorService.activeEditor; const activeResource = activeInput ? activeInput.getResource() : undefined; if (activeResource) { @@ -498,7 +495,7 @@ export class GlobalCompareResourcesAction extends Action { override: this.editorService.openEditor({ leftResource: activeResource, rightResource: resource - }).then(() => undefined) + }) }; } @@ -506,20 +503,17 @@ export class GlobalCompareResourcesAction extends Action { }); // Bring up quick open - this.quickOpenService.show('', { autoFocus: { autoFocusSecondEntry: true } }).then(() => { - toDispose.dispose(); // make sure to unbind if quick open is closing - }); + await this.quickOpenService.show('', { autoFocus: { autoFocusSecondEntry: true } }); + toDispose.dispose(); // make sure to unbind if quick open is closing } else { this.notificationService.info(nls.localize('openFileToCompare', "Open a file first to compare it with another file.")); } - - return Promise.resolve(true); } } export class ToggleAutoSaveAction extends Action { - public static readonly ID = 'workbench.action.toggleAutoSave'; - public static readonly LABEL = nls.localize('toggleAutoSave', "Toggle Auto Save"); + static readonly ID = 'workbench.action.toggleAutoSave'; + static readonly LABEL = nls.localize('toggleAutoSave', "Toggle Auto Save"); constructor( id: string, @@ -529,7 +523,7 @@ export class ToggleAutoSaveAction extends Action { super(id, label); } - public run(): Promise { + run(): Promise { const setting = this.configurationService.inspect('files.autoSave'); let userAutoSaveConfig = setting.user; if (types.isUndefinedOrNull(userAutoSaveConfig)) { @@ -589,20 +583,21 @@ export abstract class BaseSaveAllAction extends Action { } } - public run(context?: any): Promise { - return this.doRun(context).then(() => true, error => { + async run(context?: any): Promise { + try { + await this.doRun(context); + } catch (error) { onError(this.notificationService, error); - return false; - }); + } } } export class SaveAllAction extends BaseSaveAllAction { - public static readonly ID = 'workbench.action.files.saveAll'; - public static readonly LABEL = SAVE_ALL_LABEL; + static readonly ID = 'workbench.action.files.saveAll'; + static readonly LABEL = SAVE_ALL_LABEL; - public get class(): string { + get class(): string { return 'explorer-action codicon-save-all'; } @@ -617,10 +612,10 @@ export class SaveAllAction extends BaseSaveAllAction { export class SaveAllInGroupAction extends BaseSaveAllAction { - public static readonly ID = 'workbench.files.action.saveAllInGroup'; - public static readonly LABEL = nls.localize('saveAllInGroup', "Save All in Group"); + static readonly ID = 'workbench.files.action.saveAllInGroup'; + static readonly LABEL = nls.localize('saveAllInGroup', "Save All in Group"); - public get class(): string { + get class(): string { return 'explorer-action codicon-save-all'; } @@ -635,22 +630,22 @@ export class SaveAllInGroupAction extends BaseSaveAllAction { export class CloseGroupAction extends Action { - public static readonly ID = 'workbench.files.action.closeGroup'; - public static readonly LABEL = nls.localize('closeGroup', "Close Group"); + static readonly ID = 'workbench.files.action.closeGroup'; + static readonly LABEL = nls.localize('closeGroup', "Close Group"); constructor(id: string, label: string, @ICommandService private readonly commandService: ICommandService) { super(id, label, 'codicon-close-all'); } - public run(context?: any): Promise { + run(context?: any): Promise { return this.commandService.executeCommand(CLOSE_EDITORS_AND_GROUP_COMMAND_ID, {}, context); } } export class FocusFilesExplorer extends Action { - public static readonly ID = 'workbench.files.action.focusFilesExplorer'; - public static readonly LABEL = nls.localize('focusFilesExplorer', "Focus on Files Explorer"); + static readonly ID = 'workbench.files.action.focusFilesExplorer'; + static readonly LABEL = nls.localize('focusFilesExplorer', "Focus on Files Explorer"); constructor( id: string, @@ -660,15 +655,15 @@ export class FocusFilesExplorer extends Action { super(id, label); } - public run(): Promise { + run(): Promise { return this.viewletService.openViewlet(VIEWLET_ID, true); } } export class ShowActiveFileInExplorer extends Action { - public static readonly ID = 'workbench.files.action.showActiveFileInExplorer'; - public static readonly LABEL = nls.localize('showInExplorer', "Reveal Active File in Side Bar"); + static readonly ID = 'workbench.files.action.showActiveFileInExplorer'; + static readonly LABEL = nls.localize('showInExplorer', "Reveal Active File in Side Bar"); constructor( id: string, @@ -680,7 +675,7 @@ export class ShowActiveFileInExplorer extends Action { super(id, label); } - public run(): Promise { + run(): Promise { const resource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); if (resource) { this.commandService.executeCommand(REVEAL_IN_EXPLORER_COMMAND_ID, resource); @@ -694,8 +689,8 @@ export class ShowActiveFileInExplorer extends Action { export class CollapseExplorerView extends Action { - public static readonly ID = 'workbench.files.action.collapseExplorerFolders'; - public static readonly LABEL = nls.localize('collapseExplorerFolders', "Collapse Folders in Explorer"); + static readonly ID = 'workbench.files.action.collapseExplorerFolders'; + static readonly LABEL = nls.localize('collapseExplorerFolders', "Collapse Folders in Explorer"); constructor(id: string, label: string, @@ -709,20 +704,19 @@ export class CollapseExplorerView extends Action { })); } - run(): Promise { - return this.viewletService.openViewlet(VIEWLET_ID).then((viewlet: ExplorerViewlet) => { - const explorerView = viewlet.getExplorerView(); - if (explorerView) { - explorerView.collapseAll(); - } - }); + async run(): Promise { + const explorerViewlet = await this.viewletService.openViewlet(VIEWLET_ID) as ExplorerViewlet; + const explorerView = explorerViewlet.getExplorerView(); + if (explorerView) { + explorerView.collapseAll(); + } } } export class RefreshExplorerView extends Action { - public static readonly ID = 'workbench.files.action.refreshFilesExplorer'; - public static readonly LABEL = nls.localize('refreshExplorer', "Refresh Explorer"); + static readonly ID = 'workbench.files.action.refreshFilesExplorer'; + static readonly LABEL = nls.localize('refreshExplorer', "Refresh Explorer"); constructor( @@ -737,17 +731,16 @@ export class RefreshExplorerView extends Action { })); } - public run(): Promise { - return this.viewletService.openViewlet(VIEWLET_ID).then(() => - this.explorerService.refresh() - ); + async run(): Promise { + await this.viewletService.openViewlet(VIEWLET_ID); + this.explorerService.refresh(); } } export class ShowOpenedFileInNewWindow extends Action { - public static readonly ID = 'workbench.action.files.showOpenedFileInNewWindow'; - public static readonly LABEL = nls.localize('openFileInNewWindow', "Open Active File in New Window"); + static readonly ID = 'workbench.action.files.showOpenedFileInNewWindow'; + static readonly LABEL = nls.localize('openFileInNewWindow', "Open Active File in New Window"); constructor( id: string, @@ -760,7 +753,7 @@ export class ShowOpenedFileInNewWindow extends Action { super(id, label); } - public run(): Promise { + run(): Promise { const fileResource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); if (fileResource) { if (this.fileService.canHandleResource(fileResource)) { @@ -795,14 +788,15 @@ export function validateFileName(item: ExplorerItem, name: string): string | nul if (name !== item.name) { // Do not allow to overwrite existing file - const child = parent && parent.getChild(name); + const child = parent?.getChild(name); if (child && child !== item) { return nls.localize('fileNameExistsError', "A file or folder **{0}** already exists at this location. Please choose a different name.", name); } } // Invalid File name - if (names.some((folderName) => !extpath.isValidBasename(folderName))) { + const windowsBasenameValidity = item.resource.scheme === Schemas.file && isWindows; + if (names.some((folderName) => !extpath.isValidBasename(folderName, windowsBasenameValidity))) { return nls.localize('invalidFileNameError', "The name **{0}** is not valid as a file or folder name. Please choose a different name.", trimLongName(name)); } @@ -810,7 +804,7 @@ export function validateFileName(item: ExplorerItem, name: string): string | nul } function trimLongName(name: string): string { - if (name && name.length > 255) { + if (name?.length > 255) { return `${name.substr(0, 255)}...`; } @@ -835,8 +829,8 @@ export function getWellFormedFileName(filename: string): string { export class CompareWithClipboardAction extends Action { - public static readonly ID = 'workbench.files.action.compareWithClipboard'; - public static readonly LABEL = nls.localize('compareWithClipboard', "Compare Active File with Clipboard"); + static readonly ID = 'workbench.files.action.compareWithClipboard'; + static readonly LABEL = nls.localize('compareWithClipboard', "Compare Active File with Clipboard"); private static readonly SCHEME = 'clipboardCompare'; @@ -855,7 +849,7 @@ export class CompareWithClipboardAction extends Action { this.enabled = true; } - public run(): Promise { + run(): Promise { const resource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); if (resource && (this.fileService.canHandleResource(resource) || resource.scheme === Schemas.untitled)) { if (!this.registrationDisposal) { @@ -875,7 +869,7 @@ export class CompareWithClipboardAction extends Action { return Promise.resolve(true); } - public dispose(): void { + dispose(): void { super.dispose(); dispose(this.registrationDisposal); @@ -978,15 +972,15 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole CommandsRegistry.registerCommand({ id: NEW_FILE_COMMAND_ID, - handler: (accessor) => { - openExplorerAndCreate(accessor, false).then(undefined, onUnexpectedError); + handler: async (accessor) => { + await openExplorerAndCreate(accessor, false); } }); CommandsRegistry.registerCommand({ id: NEW_FOLDER_COMMAND_ID, - handler: (accessor) => { - openExplorerAndCreate(accessor, true).then(undefined, onUnexpectedError); + handler: async (accessor) => { + await openExplorerAndCreate(accessor, true); } }); diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index b407ff167a..c9642cd575 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -214,7 +214,7 @@ async function doSave( // Pin the active editor if we are saving it const activeControl = editorService.activeControl; - const activeEditorResource = activeControl && activeControl.input && activeControl.input.getResource(); + const activeEditorResource = activeControl?.input?.getResource(); if (activeControl && activeEditorResource && isEqual(activeEditorResource, resource)) { activeControl.group.pinEditor(activeControl.input); } @@ -273,7 +273,7 @@ async function saveAll(saveAllArguments: any, editorService: IEditorService, unt const replacementPairs: IResourceEditorReplacement[] = []; inputs.forEach(i => { const targetResult = result.results.filter(r => r.success && isEqual(r.source, i.resource)).pop(); - if (targetResult && targetResult.target) { + if (targetResult?.target) { // i.resource = targetResult.target;let editor = i; const editor = i; const replacement: IResourceInput = { @@ -345,7 +345,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: undefined, weight: KeybindingWeight.WorkbenchContrib, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_D), - handler: (accessor, resource: URI | object) => { + handler: async (accessor, resource: URI | object) => { const instantiationService = accessor.get(IInstantiationService); const textModelService = accessor.get(ITextModelService); const editorService = accessor.get(IEditorService); @@ -367,8 +367,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const name = basename(uri); const editorLabel = nls.localize('modifiedLabel', "{0} (in file) ↔ {1}", name, name); - TextFileContentProvider.open(uri, COMPARE_WITH_SAVED_SCHEMA, editorLabel, editorService).then(() => { - + try { + await TextFileContentProvider.open(uri, COMPARE_WITH_SAVED_SCHEMA, editorLabel, editorService); // Dispose once no more diff editor is opened with the scheme if (registerEditorListener) { providerDisposables.push(editorService.onDidVisibleEditorsChange(() => { @@ -377,12 +377,10 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } })); } - }, error => { + } catch { providerDisposables = dispose(providerDisposables); - }); + } } - - return Promise.resolve(true); } }); diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 201adc49a4..40b9527007 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -244,17 +244,20 @@ configurationRegistry.registerConfiguration({ 'default': 'utf8', 'description': nls.localize('encoding', "The default character set encoding to use when reading and writing files. This setting can also be configured per language."), 'scope': ConfigurationScope.RESOURCE, - 'enumDescriptions': Object.keys(SUPPORTED_ENCODINGS).map(key => SUPPORTED_ENCODINGS[key].labelLong) + 'enumDescriptions': Object.keys(SUPPORTED_ENCODINGS).map(key => SUPPORTED_ENCODINGS[key].labelLong), + 'included': Object.keys(SUPPORTED_ENCODINGS).length > 1 }, 'files.autoGuessEncoding': { 'type': 'boolean', 'overridable': true, 'default': false, 'description': nls.localize('autoGuessEncoding', "When enabled, the editor will attempt to guess the character set encoding when opening files. This setting can also be configured per language."), - 'scope': ConfigurationScope.RESOURCE + 'scope': ConfigurationScope.RESOURCE, + 'included': Object.keys(SUPPORTED_ENCODINGS).length > 1 }, 'files.eol': { 'type': 'string', + 'overridable': true, 'enum': [ '\n', '\r\n', diff --git a/src/vs/workbench/contrib/files/browser/files.ts b/src/vs/workbench/contrib/files/browser/files.ts index ca307d36a5..26c8e6ddaa 100644 --- a/src/vs/workbench/contrib/files/browser/files.ts +++ b/src/vs/workbench/contrib/files/browser/files.ts @@ -20,7 +20,7 @@ export function getResourceForCommand(resource: URI | object | undefined, listSe } let list = listService.lastFocusedList; - if (list && list.getHTMLElement() === document.activeElement) { + if (list?.getHTMLElement() === document.activeElement) { let focus: unknown; if (list instanceof List) { const focused = list.getFocusedElements(); @@ -46,7 +46,7 @@ export function getResourceForCommand(resource: URI | object | undefined, listSe export function getMultiSelectedResources(resource: URI | object | undefined, listService: IListService, editorService: IEditorService): Array { const list = listService.lastFocusedList; - if (list && list.getHTMLElement() === document.activeElement) { + if (list?.getHTMLElement() === document.activeElement) { // Explorer if (list instanceof WorkbenchAsyncDataTree) { const selection = list.getSelection().map((fs: ExplorerItem) => fs.resource); diff --git a/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts index 2fd0c89c52..30b3fb65b8 100644 --- a/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts @@ -83,7 +83,7 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I const activeInput = this.editorService.activeEditor; if (activeInput instanceof DiffEditorInput && activeInput.originalInput instanceof ResourceEditorInput && activeInput.modifiedInput instanceof FileEditorInput) { const resource = activeInput.originalInput.getResource(); - if (resource && resource.scheme === CONFLICT_RESOLUTION_SCHEME) { + if (resource?.scheme === CONFLICT_RESOLUTION_SCHEME) { isActiveEditorSaveConflictResolution = true; activeConflictResolutionResource = activeInput.modifiedInput.getResource(); } @@ -141,7 +141,7 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I // Save Elevated if (canHandlePermissionOrReadonlyErrors && (isPermissionDenied || triedToMakeWriteable)) { - primaryActions.push(this.instantiationService.createInstance(SaveElevatedAction, model, triedToMakeWriteable)); + primaryActions.push(this.instantiationService.createInstance(SaveElevatedAction, model, !!triedToMakeWriteable)); } // Overwrite @@ -303,7 +303,7 @@ class OverwriteReadonlyAction extends Action { } } -export const acceptLocalChangesCommand = (accessor: ServicesAccessor, resource: URI) => { +export const acceptLocalChangesCommand = async (accessor: ServicesAccessor, resource: URI) => { const editorService = accessor.get(IEditorService); const resolverService = accessor.get(ITextModelService); const modelService = accessor.get(IModelService); @@ -312,35 +312,35 @@ export const acceptLocalChangesCommand = (accessor: ServicesAccessor, resource: if (!control) { return; } + const editor = control.input; const group = control.group; - resolverService.createModelReference(resource).then(async reference => { - const model = reference.object as IResolvedTextFileEditorModel; - const localModelSnapshot = model.createSnapshot(); + const reference = await resolverService.createModelReference(resource); + const model = reference.object as IResolvedTextFileEditorModel; + const localModelSnapshot = model.createSnapshot(); - clearPendingResolveSaveConflictMessages(); // hide any previously shown message about how to use these actions + clearPendingResolveSaveConflictMessages(); // hide any previously shown message about how to use these actions - // Revert to be able to save - await model.revert(); + // Revert to be able to save + await model.revert(); - // Restore user value (without loosing undo stack) - modelService.updateModel(model.textEditorModel, createTextBufferFactoryFromSnapshot(localModelSnapshot)); + // Restore user value (without loosing undo stack) + modelService.updateModel(model.textEditorModel, createTextBufferFactoryFromSnapshot(localModelSnapshot)); - // Trigger save - await model.save(); + // Trigger save + await model.save(); - // Reopen file input - await editorService.openEditor({ resource: model.getResource() }, group); + // Reopen file input + await editorService.openEditor({ resource: model.getResource() }, group); - // Clean up - group.closeEditor(editor); - editor.dispose(); - reference.dispose(); - }); + // Clean up + group.closeEditor(editor); + editor.dispose(); + reference.dispose(); }; -export const revertLocalChangesCommand = (accessor: ServicesAccessor, resource: URI) => { +export const revertLocalChangesCommand = async (accessor: ServicesAccessor, resource: URI) => { const editorService = accessor.get(IEditorService); const resolverService = accessor.get(ITextModelService); @@ -348,23 +348,23 @@ export const revertLocalChangesCommand = (accessor: ServicesAccessor, resource: if (!control) { return; } + const editor = control.input; const group = control.group; - resolverService.createModelReference(resource).then(async reference => { - const model = reference.object as ITextFileEditorModel; + const reference = await resolverService.createModelReference(resource); + const model = reference.object as ITextFileEditorModel; - clearPendingResolveSaveConflictMessages(); // hide any previously shown message about how to use these actions + clearPendingResolveSaveConflictMessages(); // hide any previously shown message about how to use these actions - // Revert on model - await model.revert(); + // Revert on model + await model.revert(); - // Reopen file input - await editorService.openEditor({ resource: model.getResource() }, group); + // Reopen file input + await editorService.openEditor({ resource: model.getResource() }, group); - // Clean up - group.closeEditor(editor); - editor.dispose(); - reference.dispose(); - }); + // Clean up + group.closeEditor(editor); + editor.dispose(); + reference.dispose(); }; diff --git a/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts index bf467d53ea..933355641e 100644 --- a/src/vs/workbench/contrib/files/browser/views/emptyView.ts +++ b/src/vs/workbench/contrib/files/browser/views/emptyView.ts @@ -6,7 +6,6 @@ import * as nls from 'vs/nls'; import * as errors from 'vs/base/common/errors'; 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'; @@ -34,7 +33,6 @@ export class EmptyView extends ViewletPanel { private button!: Button; private messageElement!: HTMLElement; - private titleElement!: HTMLElement; constructor( options: IViewletViewOptions, @@ -53,19 +51,6 @@ export class EmptyView extends ViewletPanel { this._register(this.labelService.onDidChangeFormatters(() => this.setLabels())); } - renderHeader(container: HTMLElement): void { - const twisties = document.createElement('div'); - DOM.addClasses(twisties, 'twisties', 'codicon', 'codicon-chevron-right'); - container.appendChild(twisties); - - const titleContainer = document.createElement('div'); - DOM.addClass(titleContainer, 'title'); - container.appendChild(titleContainer); - - this.titleElement = document.createElement('span'); - titleContainer.appendChild(this.titleElement); - } - protected renderBody(container: HTMLElement): void { DOM.addClass(container, 'explorer-empty-view'); container.tabIndex = 0; @@ -84,8 +69,9 @@ export class EmptyView extends ViewletPanel { if (!this.actionRunner) { return; } - const actionClass = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? AddRootFolderAction : OpenFolderAction; - const action = this.instantiationService.createInstance(actionClass, actionClass.ID, actionClass.LABEL); + const action = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE + ? this.instantiationService.createInstance(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL) + : this.instantiationService.createInstance(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL); this.actionRunner.run(action).then(() => { action.dispose(); }, err => { @@ -114,7 +100,9 @@ export class EmptyView extends ViewletPanel { container.style.backgroundColor = color ? color.toString() : ''; }, onDragOver: e => { - e.dataTransfer!.dropEffect = 'copy'; + if (e.dataTransfer) { + e.dataTransfer.dropEffect = 'copy'; + } } })); @@ -127,7 +115,7 @@ export class EmptyView extends ViewletPanel { if (this.button) { this.button.label = nls.localize('addFolder', "Add Folder"); } - this.titleElement.textContent = EmptyView.NAME; + this.updateTitle(EmptyView.NAME); } else { if (this.environmentService.configuration.remoteAuthority && !isWeb) { const hostLabel = this.labelService.getHostLabel(Schemas.vscodeRemote, this.environmentService.configuration.remoteAuthority); @@ -138,7 +126,7 @@ export class EmptyView extends ViewletPanel { if (this.button) { this.button.label = nls.localize('openFolder', "Open Folder"); } - this.titleElement.textContent = this.title; + this.updateTitle(this.title); } } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 3559ecad4d..05bf1a45d0 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -101,7 +101,7 @@ export class ExplorerView extends ViewletPanel { this.resourceMoveableToTrash = ExplorerResourceMoveableToTrash.bindTo(contextKeyService); const decorationProvider = new ExplorerDecorationsProvider(this.explorerService, contextService); - decorationService.registerDecorationsProvider(decorationProvider); + this._register(decorationService.registerDecorationsProvider(decorationProvider)); this._register(decorationProvider); this._register(this.resourceContext); } @@ -323,7 +323,7 @@ export class ExplorerView extends ViewletPanel { const explorerNavigator = new TreeResourceNavigator2(this.tree); this._register(explorerNavigator); // Open when selecting via keyboard - this._register(explorerNavigator.onDidOpenResource(e => { + this._register(explorerNavigator.onDidOpenResource(async 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. @@ -335,8 +335,7 @@ export class ExplorerView extends ViewletPanel { return; } this.telemetryService.publicLog2('workbenchActionExecuted', { id: 'workbench.files.openFile', from: 'explorer' }); - this.editorService.openEditor({ resource: selection[0].resource, options: { preserveFocus: e.editorOptions.preserveFocus, pinned: e.editorOptions.pinned } }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP) - .then(undefined, onUnexpectedError); + await this.editorService.openEditor({ resource: selection[0].resource, options: { preserveFocus: e.editorOptions.preserveFocus, pinned: e.editorOptions.pinned } }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } })); @@ -358,7 +357,7 @@ export class ExplorerView extends ViewletPanel { // React on events private onConfigurationUpdated(configuration: IFilesConfiguration, event?: IConfigurationChangeEvent): void { - this.autoReveal = configuration && configuration.explorer && configuration.explorer.autoReveal; + this.autoReveal = configuration?.explorer?.autoReveal; // Push down config updates to components of viewer let needsRefresh = false; diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index b6527bb554..4c876d23ab 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -36,8 +36,8 @@ import { IDragAndDropData, DataTransfers } from 'vs/base/browser/dnd'; import { Schemas } from 'vs/base/common/network'; import { DesktopDragAndDropData, ExternalElementsDragAndDropData, ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { isMacintosh } from 'vs/base/common/platform'; -import { IDialogService, IConfirmationResult, IConfirmation, getConfirmMessage } from 'vs/platform/dialogs/common/dialogs'; -import { ITextFileService, ITextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; +import { IDialogService, IConfirmation, getConfirmMessage } from 'vs/platform/dialogs/common/dialogs'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { URI } from 'vs/base/common/uri'; @@ -303,7 +303,7 @@ export class FilesFilter implements ITreeFilter { let needsRefresh = false; this.contextService.getWorkspace().folders.forEach(folder => { const configuration = this.configurationService.getValue({ resource: folder.uri }); - const excludesConfig: glob.IExpression = (configuration && configuration.files && configuration.files.exclude) || Object.create(null); + const excludesConfig: glob.IExpression = configuration?.files?.exclude || Object.create(null); if (!needsRefresh) { const cached = this.hiddenExpressionPerRoot.get(folder.uri.toString()); @@ -648,87 +648,72 @@ export class FileDragAndDrop implements ITreeDragAndDrop { return undefined; } - private addResources(target: ExplorerItem, resources: URI[]): Promise { + private async addResources(target: ExplorerItem, resources: URI[]): Promise { if (resources && resources.length > 0) { // Resolve target to check for name collisions and ask user - return this.fileService.resolve(target.resource).then(targetStat => { + const targetStat = await this.fileService.resolve(target.resource); - // Check for name collisions - const targetNames = new Set(); - if (targetStat.children) { - const ignoreCase = hasToIgnoreCase(target.resource); - targetStat.children.forEach(child => { - targetNames.add(ignoreCase ? child.name : child.name.toLowerCase()); - }); + // Check for name collisions + const targetNames = new Set(); + if (targetStat.children) { + const ignoreCase = hasToIgnoreCase(target.resource); + targetStat.children.forEach(child => { + targetNames.add(ignoreCase ? child.name : child.name.toLowerCase()); + }); + } + + const resourceExists = resources.some(resource => targetNames.has(!hasToIgnoreCase(resource) ? basename(resource) : basename(resource).toLowerCase())); + if (resourceExists) { + const confirm: IConfirmation = { + message: localize('confirmOverwrite', "A file or folder with the same name already exists in the destination folder. Do you want to replace it?"), + detail: localize('irreversible', "This action is irreversible!"), + primaryButton: localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), + type: 'warning' + }; + + const confirmationResult = await this.dialogService.confirm(confirm); + if (!confirmationResult.confirmed) { + return []; } + } - let overwritePromise: Promise = Promise.resolve({ confirmed: true }); - if (resources.some(resource => { - return targetNames.has(!hasToIgnoreCase(resource) ? basename(resource) : basename(resource).toLowerCase()); - })) { - const confirm: IConfirmation = { - message: localize('confirmOverwrite', "A file or folder with the same name already exists in the destination folder. Do you want to replace it?"), - detail: localize('irreversible', "This action is irreversible!"), - primaryButton: localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), - type: 'warning' - }; + // Run add in sequence + const addPromisesFactory: ITask>[] = []; + resources.forEach(resource => { + addPromisesFactory.push(async () => { + const sourceFile = resource; + const targetFile = joinPath(target.resource, basename(sourceFile)); - overwritePromise = this.dialogService.confirm(confirm); - } - - return overwritePromise.then(res => { - if (!res.confirmed) { - return []; + // if the target exists and is dirty, make sure to revert it. otherwise the dirty contents + // of the target file would replace the contents of the added file. since we already + // confirmed the overwrite before, this is OK. + if (this.textFileService.isDirty(targetFile)) { + await this.textFileService.revertAll([targetFile], { soft: true }); } - // Run add in sequence - const addPromisesFactory: ITask>[] = []; - resources.forEach(resource => { - addPromisesFactory.push(() => { - const sourceFile = resource; - const targetFile = joinPath(target.resource, basename(sourceFile)); - - // if the target exists and is dirty, make sure to revert it. otherwise the dirty contents - // of the target file would replace the contents of the added file. since we already - // confirmed the overwrite before, this is OK. - let revertPromise: Promise = Promise.resolve(null); - if (this.textFileService.isDirty(targetFile)) { - revertPromise = this.textFileService.revertAll([targetFile], { soft: true }); - } - - return revertPromise.then(() => { - const copyTarget = joinPath(target.resource, basename(sourceFile)); - return this.fileService.copy(sourceFile, copyTarget, true).then(stat => { - - // if we only add one file, just open it directly - if (resources.length === 1 && !stat.isDirectory) { - this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } }); - } - }); - }); - }); - }); - - return sequence(addPromisesFactory); + const copyTarget = joinPath(target.resource, basename(sourceFile)); + const stat = await this.fileService.copy(sourceFile, copyTarget, true); + // if we only add one file, just open it directly + if (resources.length === 1 && !stat.isDirectory) { + this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } }); + } }); }); - } - return Promise.resolve(undefined); + await sequence(addPromisesFactory); + } } - private handleExplorerDrop(data: IDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise { + private async handleExplorerDrop(data: IDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise { const elementsData = (data as ElementsDragAndDropData).elements; const items = distinctParents(elementsData, s => s.resource); const isCopy = (originalEvent.ctrlKey && !isMacintosh) || (originalEvent.altKey && isMacintosh); - let confirmPromise: Promise; - // Handle confirm setting const confirmDragAndDrop = !isCopy && this.configurationService.getValue(FileDragAndDrop.CONFIRM_DND_SETTING_KEY); if (confirmDragAndDrop) { - confirmPromise = this.dialogService.confirm({ + const confirmation = await this.dialogService.confirm({ message: items.length > 1 && items.every(s => s.isRoot) ? localize('confirmRootsMove', "Are you sure you want to change the order of multiple root folders in your workspace?") : items.length > 1 ? getConfirmMessage(localize('confirmMultiMove', "Are you sure you want to move the following {0} files?", items.length), items.map(s => s.resource)) : items[0].isRoot ? localize('confirmRootMove', "Are you sure you want to change the order of root folder '{0}' in your workspace?", items[0].name) @@ -739,27 +724,19 @@ export class FileDragAndDrop implements ITreeDragAndDrop { type: 'question', primaryButton: localize({ key: 'moveButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Move") }); - } else { - confirmPromise = Promise.resolve({ confirmed: true }); - } - return confirmPromise.then(res => { - - // Check for confirmation checkbox - let updateConfirmSettingsPromise: Promise = Promise.resolve(undefined); - if (res.confirmed && res.checkboxChecked === true) { - updateConfirmSettingsPromise = this.configurationService.updateValue(FileDragAndDrop.CONFIRM_DND_SETTING_KEY, false, ConfigurationTarget.USER); + if (!confirmation.confirmed) { + return; } - return updateConfirmSettingsPromise.then(() => { - if (res.confirmed) { - const rootDropPromise = this.doHandleRootDrop(items.filter(s => s.isRoot), target); - return Promise.all(items.filter(s => !s.isRoot).map(source => this.doHandleExplorerDrop(source, target, isCopy)).concat(rootDropPromise)).then(() => undefined); - } + // Check for confirmation checkbox + if (confirmation.checkboxChecked === true) { + await this.configurationService.updateValue(FileDragAndDrop.CONFIRM_DND_SETTING_KEY, false, ConfigurationTarget.USER); + } + } - return Promise.resolve(undefined); - }); - }); + const rootDropPromise = this.doHandleRootDrop(items.filter(s => s.isRoot), target); + await Promise.all(items.filter(s => !s.isRoot).map(source => this.doHandleExplorerDrop(source, target, isCopy)).concat(rootDropPromise)); } private doHandleRootDrop(roots: ExplorerItem[], target: ExplorerItem): Promise { @@ -795,17 +772,16 @@ export class FileDragAndDrop implements ITreeDragAndDrop { return this.workspaceEditingService.updateFolders(0, workspaceCreationData.length, workspaceCreationData); } - private doHandleExplorerDrop(source: ExplorerItem, target: ExplorerItem, isCopy: boolean): Promise { + private async doHandleExplorerDrop(source: ExplorerItem, target: ExplorerItem, isCopy: boolean): Promise { // Reuse duplicate action if user copies if (isCopy) { const incrementalNaming = this.configurationService.getValue().explorer.incrementalNaming; - return this.fileService.copy(source.resource, findValidPasteFileTarget(target, { resource: source.resource, isDirectory: source.isDirectory, allowOverwrite: false }, incrementalNaming)).then(stat => { - if (!stat.isDirectory) { - return this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } }).then(() => undefined); - } + const stat = await this.fileService.copy(source.resource, findValidPasteFileTarget(target, { resource: source.resource, isDirectory: source.isDirectory, allowOverwrite: false }, incrementalNaming)); + if (!stat.isDirectory) { + await this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } }); + } - return undefined; - }); + return; } // Otherwise move @@ -815,8 +791,9 @@ export class FileDragAndDrop implements ITreeDragAndDrop { return Promise.resolve(); } - return this.textFileService.move(source.resource, targetResource).then(undefined, error => { - + try { + await this.textFileService.move(source.resource, targetResource); + } catch (error) { // Conflict if ((error).fileOperationResult === FileOperationResult.FILE_MOVE_CONFLICT) { const confirm: IConfirmation = { @@ -827,21 +804,19 @@ export class FileDragAndDrop implements ITreeDragAndDrop { }; // Move with overwrite if the user confirms - return this.dialogService.confirm(confirm).then(res => { - if (res.confirmed) { - return this.textFileService.move(source.resource, targetResource, true /* overwrite */).then(undefined, error => this.notificationService.error(error)); + const { confirmed } = await this.dialogService.confirm(confirm); + if (confirmed) { + try { + await this.textFileService.move(source.resource, targetResource, true /* overwrite */); + } catch (error) { + this.notificationService.error(error); } - - return undefined; - }); + } } - // Any other error else { this.notificationService.error(error); } - - return undefined; - }); + } } } diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 2ee4fd13a7..947680e9da 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -49,7 +49,7 @@ export class OpenEditorsView extends ViewletPanel { private static readonly DEFAULT_VISIBLE_OPEN_EDITORS = 9; static readonly ID = 'workbench.explorer.openEditorsView'; - static NAME = nls.localize({ key: 'openEditors', comment: ['Open is an adjective'] }, "Open Editors"); + static readonly NAME = nls.localize({ key: 'openEditors', comment: ['Open is an adjective'] }, "Open Editors"); private dirtyCountElement!: HTMLElement; private listRefreshScheduler: RunOnceScheduler; @@ -185,15 +185,15 @@ export class OpenEditorsView extends ViewletPanel { this.dirtyCountElement = dom.append(count, $('.monaco-count-badge')); 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; + const background = colors.badgeBackground ? colors.badgeBackground.toString() : ''; + const foreground = colors.badgeForeground ? colors.badgeForeground.toString() : ''; + const border = colors.contrastBorder ? colors.contrastBorder.toString() : ''; this.dirtyCountElement.style.backgroundColor = background; this.dirtyCountElement.style.color = foreground; - this.dirtyCountElement.style.borderWidth = border ? '1px' : null; - this.dirtyCountElement.style.borderStyle = border ? 'solid' : null; + this.dirtyCountElement.style.borderWidth = border ? '1px' : ''; + this.dirtyCountElement.style.borderStyle = border ? 'solid' : ''; this.dirtyCountElement.style.borderColor = border; }))); diff --git a/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts b/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts index 9d67abd076..43bc61cbf2 100644 --- a/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts +++ b/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts @@ -66,7 +66,7 @@ export class DirtyFilesTracker extends Disposable implements IWorkbenchContribut // Only dirty models that are not PENDING_SAVE const model = this.textFileService.models.get(e.resource); - const shouldOpen = model && model.isDirty() && !model.hasState(ModelState.PENDING_SAVE); + const shouldOpen = model?.isDirty() && !model.hasState(ModelState.PENDING_SAVE); // Only if not open already return shouldOpen && !this.editorService.isOpen({ resource: e.resource }); diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index 40b6205f2d..0d189e94c8 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -31,8 +31,8 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { private static readonly MEMOIZER = createMemoizer(); - private preferredEncoding: string; - private preferredMode: string; + private preferredEncoding: string | undefined; + private preferredMode: string | undefined; private forceOpenAs: ForceOpenAs = ForceOpenAs.None; @@ -82,6 +82,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { private onModelOrphanedChanged(e: TextFileModelChangeEvent): void { if (e.resource.toString() === this.resource.toString()) { + FileEditorInput.MEMOIZER.clear(); this._onDidChangeLabel.fire(); } } @@ -90,7 +91,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { return this.resource; } - getEncoding(): string { + getEncoding(): string | undefined { const textModel = this.textFileService.models.get(this.resource); if (textModel) { return textModel.getEncoding(); @@ -99,7 +100,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { return this.preferredEncoding; } - getPreferredEncoding(): string { + getPreferredEncoding(): string | undefined { return this.preferredEncoding; } @@ -209,11 +210,12 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { private decorateLabel(label: string): string { const model = this.textFileService.models.get(this.resource); - if (model && model.hasState(ModelState.ORPHAN)) { + + if (model?.hasState(ModelState.ORPHAN)) { return localize('orphanedFile', "{0} (deleted)", label); } - if (model && model.isReadonly()) { + if (model?.isReadonly()) { return localize('readonlyFile', "{0} (read-only)", label); } diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index 8ba091f964..09bf8a88c2 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -23,7 +23,7 @@ function getFileEventsExcludes(configurationService: IConfigurationService, root const scope = root ? { resource: root } : undefined; const configuration = scope ? configurationService.getValue(scope) : configurationService.getValue(); - return (configuration && configuration.files && configuration.files.exclude) || Object.create(null); + return configuration?.files?.exclude || Object.create(null); } export class ExplorerService implements IExplorerService { @@ -377,7 +377,7 @@ export class ExplorerService implements IExplorerService { } private onConfigurationUpdated(configuration: IFilesConfiguration, event?: IConfigurationChangeEvent): void { - const configSortOrder = configuration && configuration.explorer && configuration.explorer.sortOrder || 'default'; + const configSortOrder = configuration?.explorer?.sortOrder || 'default'; if (this._sortOrder !== configSortOrder) { const shouldRefresh = this._sortOrder !== undefined; this._sortOrder = configSortOrder; diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index 1c192671aa..2db4997e4f 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -6,7 +6,7 @@ import { URI } from 'vs/base/common/uri'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IWorkbenchEditorConfiguration, IEditorIdentifier, IEditorInput, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; -import { IFilesConfiguration, FileChangeType, IFileService } from 'vs/platform/files/common/files'; +import { IFilesConfiguration as PlatformIFilesConfiguration, FileChangeType, IFileService } from 'vs/platform/files/common/files'; import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; @@ -102,7 +102,7 @@ export const FILE_EDITOR_INPUT_ID = 'workbench.editors.files.fileEditorInput'; export const BINARY_FILE_EDITOR_ID = 'workbench.editors.files.binaryFileEditor'; -export interface IFilesConfiguration extends IFilesConfiguration, IWorkbenchEditorConfiguration { +export interface IFilesConfiguration extends PlatformIFilesConfiguration, IWorkbenchEditorConfiguration { explorer: { openEditors: { visible: number; diff --git a/src/vs/workbench/contrib/files/common/workspaceWatcher.ts b/src/vs/workbench/contrib/files/common/workspaceWatcher.ts index 16a21dd4bb..c04f44c6c1 100644 --- a/src/vs/workbench/contrib/files/common/workspaceWatcher.ts +++ b/src/vs/workbench/contrib/files/common/workspaceWatcher.ts @@ -107,7 +107,7 @@ export class WorkspaceWatcher extends Disposable { // Compute the watcher exclude rules from configuration const excludes: string[] = []; const config = this.configurationService.getValue({ resource }); - if (config.files && config.files.watcherExclude) { + if (config.files?.watcherExclude) { for (const key in config.files.watcherExclude) { if (config.files.watcherExclude[key] === true) { excludes.push(key); diff --git a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts index 7680abee36..eca779ecbf 100644 --- a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts +++ b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts @@ -33,7 +33,7 @@ type FormattingEditProvider = DocumentFormattingEditProvider | DocumentRangeForm class DefaultFormatter extends Disposable implements IWorkbenchContribution { - static configName = 'editor.defaultFormatter'; + static readonly configName = 'editor.defaultFormatter'; static extensionIds: (string | null)[] = []; static extensionDescriptions: string[] = []; @@ -126,10 +126,10 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution { } private async _pickAndPersistDefaultFormatter(formatter: T[], document: ITextModel): Promise { - const picks = formatter.map((formatter, index) => { - return { + const picks = formatter.map((formatter, index): IIndexedPick => { + return { index, - label: formatter.displayName || formatter.extensionId || '?', + label: formatter.displayName || (formatter.extensionId ? formatter.extensionId.value : '?'), description: formatter.extensionId && formatter.extensionId.value }; }); @@ -203,7 +203,7 @@ async function showFormatterPick(accessor: ServicesAccessor, model: ITextModel, const picks = formatters.map((provider, index) => { const isDefault = ExtensionIdentifier.equals(provider.extensionId, defaultFormatter); - const pick = { + const pick: IIndexedPick = { index, label: provider.displayName || '', description: isDefault ? nls.localize('def', "(default)") : undefined, diff --git a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts index d3366c4104..36d1c66a32 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts @@ -6,13 +6,11 @@ import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContribution, Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { Disposable } from 'vs/base/common/lifecycle'; import { ConfigureLocaleAction } from 'vs/workbench/contrib/localizations/browser/localizationsActions'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { IExtensionManagementService, DidInstallExtensionEvent, IExtensionGalleryService, IGalleryExtension, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -35,7 +33,6 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureLocaleAction, export class LocalizationWorkbenchContribution extends Disposable implements IWorkbenchContribution { constructor( - @ILocalizationsService private readonly localizationService: ILocalizationsService, @INotificationService private readonly notificationService: INotificationService, @IJSONEditingService private readonly jsonEditingService: IJSONEditingService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @@ -47,26 +44,10 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo @ITelemetryService private readonly telemetryService: ITelemetryService ) { super(); - this.updateLocaleDefintionSchema(); this.checkAndInstall(); - this._register(this.localizationService.onDidLanguagesChange(() => this.updateLocaleDefintionSchema())); this._register(this.extensionManagementService.onDidInstallExtension(e => this.onDidInstallExtension(e))); } - private updateLocaleDefintionSchema(): void { - this.localizationService.getLanguageIds() - .then(languageIds => { - let lowercaseLanguageIds: string[] = []; - languageIds.forEach((languageId) => { - let lowercaseLanguageId = languageId.toLowerCase(); - if (lowercaseLanguageId !== languageId) { - lowercaseLanguageIds.push(lowercaseLanguageId); - } - }); - registerLocaleDefinitionSchema([...languageIds, ...lowercaseLanguageIds]); - }); - } - private onDidInstallExtension(e: DidInstallExtensionEvent): void { if (e.local && e.operation === InstallOperation.Install && e.local.manifest.contributes && e.local.manifest.contributes.localizations && e.local.manifest.contributes.localizations.length) { const locale = e.local.manifest.contributes.localizations[0].languageId; @@ -80,7 +61,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo [{ label: updateAndRestart ? localize('yes', "Yes") : localize('restart now', "Restart Now"), run: () => { - const updatePromise = updateAndRestart ? this.jsonEditingService.write(this.environmentService.localeResource, [{ key: 'locale', value: locale }], true) : Promise.resolve(undefined); + const updatePromise = updateAndRestart ? this.jsonEditingService.write(this.environmentService.argvResource, [{ key: 'locale', value: locale }], true) : Promise.resolve(undefined); updatePromise.then(() => this.hostService.restart(), e => this.notificationService.error(e)); } }], @@ -226,31 +207,6 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo } } -function registerLocaleDefinitionSchema(languages: string[]): void { - const localeDefinitionFileSchemaId = 'vscode://schemas/locale'; - const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); - // Keep en-US since we generated files with that content. - jsonRegistry.registerSchema(localeDefinitionFileSchemaId, { - id: localeDefinitionFileSchemaId, - allowComments: true, - allowTrailingCommas: true, - description: 'Locale Definition file', - type: 'object', - default: { - 'locale': 'en' - }, - required: ['locale'], - properties: { - locale: { - type: 'string', - enum: languages, - description: localize('JsonSchema.locale', 'The UI Language to use.') - } - } - }); -} - -registerLocaleDefinitionSchema(platform.language ? [platform.language] : []); const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(LocalizationWorkbenchContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts b/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts index eadcbe2694..effc89cf54 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts @@ -65,7 +65,7 @@ export class ConfigureLocaleAction extends Action { } if (selectedLanguage) { - await this.jsonEditingService.write(this.environmentService.localeResource, [{ key: 'locale', value: selectedLanguage.label }], true); + await this.jsonEditingService.write(this.environmentService.argvResource, [{ key: 'locale', value: selectedLanguage.label }], true); const restart = await this.dialogService.confirm({ type: 'info', message: localize('relaunchDisplayLanguageMessage', "A restart is required for the change in display language to take effect."), diff --git a/src/vs/workbench/contrib/logs/common/logsActions.ts b/src/vs/workbench/contrib/logs/common/logsActions.ts index 7403a4c861..44d764e99f 100644 --- a/src/vs/workbench/contrib/logs/common/logsActions.ts +++ b/src/vs/workbench/contrib/logs/common/logsActions.ts @@ -15,8 +15,8 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic export class SetLogLevelAction extends Action { - static ID = 'workbench.action.setLogLevel'; - static LABEL = nls.localize('setLogLevel', "Set Log Level..."); + static readonly ID = 'workbench.action.setLogLevel'; + static readonly LABEL = nls.localize('setLogLevel', "Set Log Level..."); constructor(id: string, label: string, @IQuickInputService private readonly quickInputService: IQuickInputService, @@ -60,8 +60,8 @@ export class SetLogLevelAction extends Action { export class OpenWindowSessionLogFileAction extends Action { - static ID = 'workbench.action.openSessionLogFile'; - static LABEL = nls.localize('openSessionLogFile', "Open Window Log File (Session)..."); + static readonly ID = 'workbench.action.openSessionLogFile'; + static readonly LABEL = nls.localize('openSessionLogFile', "Open Window Log File (Session)..."); constructor(id: string, label: string, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, diff --git a/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts b/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts index 0f16a556c8..b4c3dc821e 100644 --- a/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts +++ b/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts @@ -12,8 +12,8 @@ import { IElectronService } from 'vs/platform/electron/node/electron'; export class OpenLogsFolderAction extends Action { - static ID = 'workbench.action.openLogsFolder'; - static LABEL = nls.localize('openLogsFolder', "Open Logs Folder"); + static readonly ID = 'workbench.action.openLogsFolder'; + static readonly LABEL = nls.localize('openLogsFolder', "Open Logs Folder"); constructor(id: string, label: string, @IEnvironmentService private readonly environmentService: IEnvironmentService, diff --git a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts index a6fa78298f..746e5903e8 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts @@ -196,14 +196,14 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { private createBadge(container: HTMLElement): void { const filterBadge = this.filterBadge = DOM.append(container, DOM.$('.markers-panel-filter-badge')); 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; + const background = colors.badgeBackground ? colors.badgeBackground.toString() : ''; + const foreground = colors.badgeForeground ? colors.badgeForeground.toString() : ''; + const border = colors.contrastBorder ? colors.contrastBorder.toString() : ''; filterBadge.style.backgroundColor = background; - filterBadge.style.borderWidth = border ? '1px' : null; - filterBadge.style.borderStyle = border ? 'solid' : null; + filterBadge.style.borderWidth = border ? '1px' : ''; + filterBadge.style.borderStyle = border ? 'solid' : ''; filterBadge.style.borderColor = border; filterBadge.style.color = foreground; })); diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 33d1a38b53..c518ff7cbb 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -267,7 +267,7 @@ class MarkerWidget extends Disposable { this.disposables.clear(); dom.clearNode(this.messageAndDetailsContainer); - this.icon.className = `marker-icon ${SeverityIcon.className(MarkerSeverity.toSeverity(element.marker.severity))}`; + this.icon.className = `marker-icon codicon ${SeverityIcon.className(MarkerSeverity.toSeverity(element.marker.severity))}`; this.renderQuickfixActionbar(element); this.renderMultilineActionbar(element); @@ -302,7 +302,7 @@ class MarkerWidget extends Disposable { const action = new Action('problems.action.toggleMultiline'); action.enabled = !!viewModel && marker.lines.length > 1; action.tooltip = multiline ? localize('single line', "Show message in single line") : localize('multi line', "Show message in multiple lines"); - action.class = multiline ? 'octicon octicon-chevron-up' : 'octicon octicon-chevron-down'; + action.class = multiline ? 'codicon codicon-chevron-up' : 'codicon codicon-chevron-down'; action.run = () => { if (viewModel) { viewModel.multiline = !viewModel.multiline; } return Promise.resolve(); }; this.multilineActionbar.push([action], { icon: true, label: false }); } diff --git a/src/vs/workbench/contrib/markers/browser/media/markers.css b/src/vs/workbench/contrib/markers/browser/media/markers.css index dd21e3bd6f..11a6c524b3 100644 --- a/src/vs/workbench/contrib/markers/browser/media/markers.css +++ b/src/vs/workbench/contrib/markers/browser/media/markers.css @@ -132,9 +132,15 @@ margin-left: 6px; } -.markers-panel .monaco-tl-contents .marker-icon, -.markers-panel .monaco-tl-contents .actions .action-item { +.markers-panel .monaco-tl-contents .marker-icon { margin-right: 6px; + display: flex; + align-items: center; + justify-content: center; +} + +.markers-panel .monaco-tl-contents .actions .action-item { + margin-right: 2px; } .markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-source, diff --git a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts index a102ac7dd0..893dca149b 100644 --- a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts +++ b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts @@ -50,6 +50,136 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'description': localize('outline.problems.badges', "Use badges for Errors & Warnings."), 'type': 'boolean', 'default': true + }, + 'outline.filteredTypes.file': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.file', "When set to `false` outline never shows `file`-symbols.") + }, + 'outline.filteredTypes.module': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.module', "When set to `false` outline never shows `module`-symbols.") + }, + 'outline.filteredTypes.namespace': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.namespace', "When set to `false` outline never shows `namespace`-symbols.") + }, + 'outline.filteredTypes.package': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.package', "When set to `false` outline never shows `package`-symbols.") + }, + 'outline.filteredTypes.class': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.class', "When set to `false` outline never shows `class`-symbols.") + }, + 'outline.filteredTypes.method': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.method', "When set to `false` outline never shows `method`-symbols.") + }, + 'outline.filteredTypes.property': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.property', "When set to `false` outline never shows `property`-symbols.") + }, + 'outline.filteredTypes.field': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.field', "When set to `false` outline never shows `field`-symbols.") + }, + 'outline.filteredTypes.constructor': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.constructor', "When set to `false` outline never shows `constructor`-symbols.") + }, + 'outline.filteredTypes.enum': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.enum', "When set to `false` outline never shows `enum`-symbols.") + }, + 'outline.filteredTypes.interface': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.interface', "When set to `false` outline never shows `interface`-symbols.") + }, + 'outline.filteredTypes.function': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.function', "When set to `false` outline never shows `function`-symbols.") + }, + 'outline.filteredTypes.variable': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.variable', "When set to `false` outline never shows `variable`-symbols.") + }, + 'outline.filteredTypes.constant': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.constant', "When set to `false` outline never shows `constant`-symbols.") + }, + 'outline.filteredTypes.string': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.string', "When set to `false` outline never shows `string`-symbols.") + }, + 'outline.filteredTypes.number': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.number', "When set to `false` outline never shows `number`-symbols.") + }, + 'outline.filteredTypes.boolean': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.boolean', "When set to `false` outline never shows `boolean`-symbols.") + }, + 'outline.filteredTypes.array': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.array', "When set to `false` outline never shows `array`-symbols.") + }, + 'outline.filteredTypes.object': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.object', "When set to `false` outline never shows `object`-symbols.") + }, + 'outline.filteredTypes.key': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.key', "When set to `false` outline never shows `key`-symbols.") + }, + 'outline.filteredTypes.null': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.null', "When set to `false` outline never shows `null`-symbols.") + }, + 'outline.filteredTypes.enumMember': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.enumMember', "When set to `false` outline never shows `enumMember`-symbols.") + }, + 'outline.filteredTypes.struct': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.struct', "When set to `false` outline never shows `struct`-symbols.") + }, + 'outline.filteredTypes.event': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.event', "When set to `false` outline never shows `event`-symbols.") + }, + 'outline.filteredTypes.operator': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.operator', "When set to `false` outline never shows `operator`-symbols.") + }, + 'outline.filteredTypes.typeParameter': { + type: 'boolean', + default: true, + markdownDescription: localize('filteredTypes.typeParameter', "When set to `false` outline never shows `typeParameter`-symbols.") } } }); diff --git a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts index eb074dae00..16792ed2c1 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts @@ -40,7 +40,7 @@ import { CollapseAction } from 'vs/workbench/browser/viewlet'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { OutlineConfigKeys, OutlineViewFocused, OutlineViewFiltered } from 'vs/editor/contrib/documentSymbols/outline'; import { FuzzyScore } from 'vs/base/common/filters'; -import { OutlineDataSource, OutlineItemComparator, OutlineSortOrder, OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItem, OutlineIdentityProvider, OutlineNavigationLabelProvider } from 'vs/editor/contrib/documentSymbols/outlineTree'; +import { OutlineDataSource, OutlineItemComparator, OutlineSortOrder, OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItem, OutlineIdentityProvider, OutlineNavigationLabelProvider, OutlineFilter } from 'vs/editor/contrib/documentSymbols/outlineTree'; import { IDataTreeViewState } from 'vs/base/browser/ui/tree/dataTree'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { basename } from 'vs/base/common/resources'; @@ -247,10 +247,9 @@ export class OutlinePanel extends ViewletPanel { private _treeDataSource!: OutlineDataSource; private _treeRenderer!: OutlineElementRenderer; private _treeComparator!: OutlineItemComparator; + private _treeFilter!: OutlineFilter; private _treeStates = new LRUCache(10); - private _treeFakeUIEvent = new UIEvent('me'); - private readonly _contextKeyFocused: IContextKey; private readonly _contextKeyFiltered: IContextKey; @@ -315,6 +314,7 @@ export class OutlinePanel extends ViewletPanel { this._treeRenderer = this._instantiationService.createInstance(OutlineElementRenderer); this._treeDataSource = new OutlineDataSource(); this._treeComparator = new OutlineItemComparator(this._outlineViewState.sortBy); + this._treeFilter = this._instantiationService.createInstance(OutlineFilter, 'outline.filteredTypes'); this._tree = this._instantiationService.createInstance( WorkbenchDataTree, 'OutlinePanel', @@ -328,6 +328,7 @@ export class OutlinePanel extends ViewletPanel { multipleSelectionSupport: false, filterOnType: this._outlineViewState.filterOnType, sorter: this._treeComparator, + filter: this._treeFilter, identityProvider: new OutlineIdentityProvider(), keyboardNavigationLabelProvider: new OutlineNavigationLabelProvider() } @@ -366,6 +367,10 @@ export class OutlinePanel extends ViewletPanel { if (e.affectsConfiguration(OutlineConfigKeys.icons)) { this._tree.updateChildren(); } + if (e.affectsConfiguration('outline.filteredTypes')) { + this._treeFilter.update(); + this._tree.refilter(); + } })); this._register(this.onDidChangeBodyVisibility(visible => { @@ -447,7 +452,6 @@ export class OutlinePanel extends ViewletPanel { private async _doUpdate(editor: ICodeEditor | undefined, event: IModelContentChangedEvent | undefined): Promise { this._editorDisposables.clear(); - this._progressBar.infinite().show(150); const oldModel = this._tree.getInput(); @@ -460,16 +464,16 @@ export class OutlinePanel extends ViewletPanel { return this._showMessage(localize('no-editor', "The active editor cannot provide outline information.")); } - let textModel = editor.getModel(); - let loadingMessage: IDisposable | undefined; - if (!oldModel) { - loadingMessage = new TimeoutTimer( - () => this._showMessage(localize('loading', "Loading document symbols for '{0}'...", basename(textModel.uri))), - 100 - ); - } + const textModel = editor.getModel(); + const loadingMessage = oldModel && new TimeoutTimer( + () => this._showMessage(localize('loading', "Loading document symbols for '{0}'...", basename(textModel.uri))), + 100 + ); - let createdModel = await OutlinePanel._createOutlineModel(textModel, this._editorDisposables); + const requestDelay = OutlineModel.getRequestDelay(textModel); + this._progressBar.infinite().show(requestDelay); + + const createdModel = await OutlinePanel._createOutlineModel(textModel, this._editorDisposables); dispose(loadingMessage); if (!createdModel) { return; @@ -519,7 +523,7 @@ export class OutlinePanel extends ViewletPanel { newModel = oldModel; } else { let state = this._treeStates.get(newModel.textModel.uri.toString()); - await this._tree.setInput(newModel, state); + this._tree.setInput(newModel, state); } // transfer focus from domNode to the tree @@ -531,10 +535,8 @@ export class OutlinePanel extends ViewletPanel { // feature: reveal outline selection in editor // on change -> reveal/select defining range - this._editorDisposables.add(this._tree.onDidChangeSelection(e => { - if (e.browserEvent === this._treeFakeUIEvent /* || e.payload && e.payload.didClickOnTwistie */) { - return; - } + this._editorDisposables.add(this._tree.onDidOpen(e => { + let [first] = e.elements; if (!(first instanceof OutlineElement)) { return; @@ -636,7 +638,7 @@ export class OutlinePanel extends ViewletPanel { if (top === null) { this._tree.reveal(item, 0.5); } - this._tree.setFocus([item], this._treeFakeUIEvent); - this._tree.setSelection([item], this._treeFakeUIEvent); + this._tree.setFocus([item]); + this._tree.setSelection([item]); } } diff --git a/src/vs/workbench/contrib/output/browser/outputActions.ts b/src/vs/workbench/contrib/output/browser/outputActions.ts index 2099edf33b..ca321d4bb3 100644 --- a/src/vs/workbench/contrib/output/browser/outputActions.ts +++ b/src/vs/workbench/contrib/output/browser/outputActions.ts @@ -213,8 +213,8 @@ export class OpenLogOutputFile extends Action { export class ShowLogsOutputChannelAction extends Action { - static ID = 'workbench.action.showLogs'; - static LABEL = nls.localize('showLogs', "Show Logs..."); + static readonly ID = 'workbench.action.showLogs'; + static readonly LABEL = nls.localize('showLogs', "Show Logs..."); constructor(id: string, label: string, @IQuickInputService private readonly quickInputService: IQuickInputService, @@ -243,8 +243,8 @@ interface IOutputChannelQuickPickItem extends IQuickPickItem { export class OpenOutputLogFileAction extends Action { - static ID = 'workbench.action.openLogFile'; - static LABEL = nls.localize('openLogFile', "Open Log File..."); + static readonly ID = 'workbench.action.openLogFile'; + static readonly LABEL = nls.localize('openLogFile', "Open Log File..."); constructor(id: string, label: string, @IQuickInputService private readonly quickInputService: IQuickInputService, diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts index 7d037c36bd..40da268cd5 100644 --- a/src/vs/workbench/contrib/output/browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -155,7 +155,7 @@ export class OutputService extends Disposable implements IOutputService, ITextMo } } - private onDidPanelOpen(panel: IPanel | null, preserveFocus: boolean): Promise { + private onDidPanelOpen(panel: IPanel | undefined, preserveFocus: boolean): Promise { if (panel && panel.getId() === OUTPUT_PANEL_ID) { this._outputPanel = this.panelService.getActivePanel(); if (this.activeChannel) { diff --git a/src/vs/workbench/contrib/output/common/outputLinkComputer.ts b/src/vs/workbench/contrib/output/common/outputLinkComputer.ts index 17b9e8933c..4e1aeaa3f1 100644 --- a/src/vs/workbench/contrib/output/common/outputLinkComputer.ts +++ b/src/vs/workbench/contrib/output/common/outputLinkComputer.ts @@ -12,6 +12,7 @@ import * as strings from 'vs/base/common/strings'; import { Range } from 'vs/editor/common/core/range'; import { isWindows } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; +import { find } from 'vs/base/common/arrays'; export interface ICreateData { workspaceFolders: string[]; @@ -44,15 +45,9 @@ export class OutputLinkComputer { }); } - private getModel(uri: string): IMirrorModel | null { + private getModel(uri: string): IMirrorModel | undefined { const models = this.ctx.getMirrorModels(); - for (const model of models) { - if (model.uri.toString() === uri) { - return model; - } - } - - return null; + return find(models, model => model.uri.toString() === uri); } public computeLinks(uri: string): Promise { diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts index 0753536523..9b466b2ac3 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts @@ -59,6 +59,9 @@ export class KeybindingsSearchWidget extends SearchWidget { super(parent, options, contextViewService, instantiationService, themeService); this._register(attachInputBoxStyler(this.inputBox, themeService)); this._register(toDisposable(() => this.stopRecordingKeys())); + this._firstPart = null; + this._chordPart = null; + this._inputValue = ''; this._reset(); } @@ -166,7 +169,45 @@ export class DefineKeybindingWidget extends Widget { @IThemeService private readonly themeService: IThemeService ) { super(); - this.create(); + + this._domNode = createFastDomNode(document.createElement('div')); + this._domNode.setDisplay('none'); + this._domNode.setClassName('defineKeybindingWidget'); + this._domNode.setWidth(DefineKeybindingWidget.WIDTH); + this._domNode.setHeight(DefineKeybindingWidget.HEIGHT); + + const message = nls.localize('defineKeybinding.initial', "Press desired key combination and then press ENTER."); + dom.append(this._domNode.domNode, dom.$('.message', undefined, message)); + + this._register(attachStylerCallback(this.themeService, { editorWidgetBackground, editorWidgetForeground, widgetShadow }, colors => { + if (colors.editorWidgetBackground) { + this._domNode.domNode.style.backgroundColor = colors.editorWidgetBackground.toString(); + } else { + this._domNode.domNode.style.backgroundColor = ''; + } + if (colors.editorWidgetForeground) { + this._domNode.domNode.style.color = colors.editorWidgetForeground.toString(); + } else { + this._domNode.domNode.style.color = null; + } + + if (colors.widgetShadow) { + this._domNode.domNode.style.boxShadow = `0 2px 8px ${colors.widgetShadow}`; + } else { + this._domNode.domNode.style.boxShadow = ''; + } + })); + + this._keybindingInputWidget = this._register(this.instantiationService.createInstance(KeybindingsSearchWidget, this._domNode.domNode, { ariaLabel: message })); + this._keybindingInputWidget.startRecordingKeys(); + this._register(this._keybindingInputWidget.onKeybinding(keybinding => this.onKeybinding(keybinding))); + this._register(this._keybindingInputWidget.onEnter(() => this.hide())); + this._register(this._keybindingInputWidget.onEscape(() => this.onCancel())); + this._register(this._keybindingInputWidget.onBlur(() => this.onCancel())); + + this._outputNode = dom.append(this._domNode.domNode, dom.$('.output')); + this._showExistingKeybindingsNode = dom.append(this._domNode.domNode, dom.$('.existing')); + if (parent) { dom.append(parent, this._domNode.domNode); } @@ -217,46 +258,6 @@ export class DefineKeybindingWidget extends Widget { } } - private create(): void { - this._domNode = createFastDomNode(document.createElement('div')); - this._domNode.setDisplay('none'); - this._domNode.setClassName('defineKeybindingWidget'); - this._domNode.setWidth(DefineKeybindingWidget.WIDTH); - this._domNode.setHeight(DefineKeybindingWidget.HEIGHT); - - const message = nls.localize('defineKeybinding.initial', "Press desired key combination and then press ENTER."); - dom.append(this._domNode.domNode, dom.$('.message', undefined, message)); - - this._register(attachStylerCallback(this.themeService, { editorWidgetBackground, editorWidgetForeground, widgetShadow }, colors => { - if (colors.editorWidgetBackground) { - this._domNode.domNode.style.backgroundColor = colors.editorWidgetBackground.toString(); - } else { - this._domNode.domNode.style.backgroundColor = null; - } - if (colors.editorWidgetForeground) { - this._domNode.domNode.style.color = colors.editorWidgetForeground.toString(); - } else { - this._domNode.domNode.style.color = null; - } - - if (colors.widgetShadow) { - this._domNode.domNode.style.boxShadow = `0 2px 8px ${colors.widgetShadow}`; - } else { - this._domNode.domNode.style.boxShadow = null; - } - })); - - this._keybindingInputWidget = this._register(this.instantiationService.createInstance(KeybindingsSearchWidget, this._domNode.domNode, { ariaLabel: message })); - this._keybindingInputWidget.startRecordingKeys(); - this._register(this._keybindingInputWidget.onKeybinding(keybinding => this.onKeybinding(keybinding))); - this._register(this._keybindingInputWidget.onEnter(() => this.hide())); - this._register(this._keybindingInputWidget.onEscape(() => this.onCancel())); - this._register(this._keybindingInputWidget.onBlur(() => this.onCancel())); - - this._outputNode = dom.append(this._domNode.domNode, dom.$('.output')); - this._showExistingKeybindingsNode = dom.append(this._domNode.domNode, dom.$('.existing')); - } - private onKeybinding(keybinding: [ResolvedKeybinding | null, ResolvedKeybinding | null]): void { const [firstPart, chordPart] = keybinding; this._firstPart = firstPart; diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index 35df5e1a7a..e05513d79d 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -29,7 +29,6 @@ import { } from 'vs/workbench/contrib/preferences/common/preferences'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; -import { List } from 'vs/base/browser/ui/list/listWidget'; import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListEvent } from 'vs/base/browser/ui/list/list'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -79,7 +78,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor private keybindingsListContainer: HTMLElement; private unAssignedKeybindingItemToRevealAndFocus: IKeybindingItemEntry | null; private listEntries: IListEntry[]; - private keybindingsList: List; + private keybindingsList: WorkbenchList; private dimension: DOM.Dimension; private delayedFiltering: Delayer; @@ -402,13 +401,13 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor const recordingBadge = DOM.append(container, DOM.$('.recording-badge.disabled')); recordingBadge.textContent = localize('recording', "Recording Keys"); this._register(attachStylerCallback(this.themeService, { badgeBackground, contrastBorder, badgeForeground }, colors => { - const background = colors.badgeBackground ? colors.badgeBackground.toString() : null; - const border = colors.contrastBorder ? colors.contrastBorder.toString() : null; - const color = colors.badgeForeground ? colors.badgeForeground.toString() : null; + const background = colors.badgeBackground ? colors.badgeBackground.toString() : ''; + const border = colors.contrastBorder ? colors.contrastBorder.toString() : ''; + const color = colors.badgeForeground ? colors.badgeForeground.toString() : ''; recordingBadge.style.backgroundColor = background; - recordingBadge.style.borderWidth = border ? '1px' : null; - recordingBadge.style.borderStyle = border ? 'solid' : null; + recordingBadge.style.borderWidth = border ? '1px' : ''; + recordingBadge.style.borderStyle = border ? 'solid' : ''; recordingBadge.style.borderColor = border; recordingBadge.style.color = color ? color.toString() : null; })); diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts index c44b863304..e5bab5617b 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts @@ -23,14 +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, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { themeColorFromId, ThemeColor } 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'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { equals } from 'vs/base/common/arrays'; 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."); @@ -39,7 +38,7 @@ const INTERESTING_FILE = /keybindings\.json$/; export class DefineKeybindingController extends Disposable implements editorCommon.IEditorContribution { - private static readonly ID = 'editor.contrib.defineKeybinding'; + public static readonly ID = 'editor.contrib.defineKeybinding'; static get(editor: ICodeEditor): DefineKeybindingController { return editor.getContribution(DefineKeybindingController.ID); @@ -58,10 +57,6 @@ export class DefineKeybindingController extends Disposable implements editorComm this._update(); } - getId(): string { - return DefineKeybindingController.ID; - } - get keybindingWidgetRenderer(): KeybindingWidgetRenderer | undefined { return this._keybindingWidgetRenderer; } @@ -267,18 +262,7 @@ export class KeybindingEditorDecorationsRenderer extends Disposable { const aParts = KeybindingParser.parseUserBinding(a); const bParts = KeybindingParser.parseUserBinding(b); - - if (aParts.length !== bParts.length) { - return false; - } - - for (let i = 0, len = aParts.length; i < len; i++) { - if (!this._userBindingEquals(aParts[i], bParts[i])) { - return false; - } - } - - return true; + return equals(aParts, bParts, (a, b) => this._userBindingEquals(a, b)); } private static _userBindingEquals(a: SimpleKeybinding | ScanCodeBinding, b: SimpleKeybinding | ScanCodeBinding): boolean { @@ -397,10 +381,5 @@ function isInterestingEditorModel(editor: ICodeEditor): boolean { return INTERESTING_FILE.test(url); } -registerEditorContribution(DefineKeybindingController); +registerEditorContribution(DefineKeybindingController.ID, 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; }`); -}); diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index cf9576ada3..62d8487bf4 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -273,7 +273,7 @@ max-width: 1000px; margin: auto; box-sizing: border-box; - padding-left: 217px; + padding-left: 219px; padding-right: 20px; overflow: visible; } diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts index 278357d9f7..3b53024ce8 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, DisposableStore } from 'vs/base/common/lifecycle'; +import { 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'; @@ -198,9 +198,6 @@ export class OpenFolderSettingsAction extends Action { static readonly ID = 'workbench.action.openFolderSettings'; static readonly LABEL = OPEN_FOLDER_SETTINGS_LABEL; - private disposables: IDisposable[] = []; - - constructor( id: string, label: string, @@ -210,8 +207,8 @@ export class OpenFolderSettingsAction extends Action { ) { super(id, label); this.update(); - this.workspaceContextService.onDidChangeWorkbenchState(() => this.update(), this, this.disposables); - this.workspaceContextService.onDidChangeWorkspaceFolders(() => this.update(), this, this.disposables); + this._register(this.workspaceContextService.onDidChangeWorkbenchState(() => this.update(), this)); + this._register(this.workspaceContextService.onDidChangeWorkspaceFolders(() => this.update(), this)); } private update(): void { @@ -228,11 +225,6 @@ export class OpenFolderSettingsAction extends Action { return undefined; }); } - - dispose(): void { - this.disposables = dispose(this.disposables); - super.dispose(); - } } export class ConfigureLanguageBasedSettingsAction extends Action { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index d768f45017..bbf8290bfa 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -17,7 +17,7 @@ import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorExtensionsRegistry, IEditorContributionCtor, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { EditorExtensionsRegistry, registerEditorContribution, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import * as editorCommon from 'vs/editor/common/editorCommon'; @@ -54,7 +54,7 @@ import { DefaultPreferencesEditorInput, PreferencesEditorInput } from 'vs/workbe import { DefaultSettingsEditorModel, SettingsEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; +import { withNullAsUndefined, withUndefinedAsNull, assertIsDefined } from 'vs/base/common/types'; export class PreferencesEditor extends BaseEditor { @@ -683,7 +683,7 @@ class PreferencesRenderersController extends Disposable { } private _updatePreference(key: string, value: any, source: ISetting, fromEditableSettings?: boolean): void { - const data: { [key: string]: any } = { + const data: { [key: string]: any; } = { userConfigurationKeys: [key] }; @@ -718,7 +718,7 @@ class PreferencesRenderersController extends Disposable { this.telemetryService.publicLog('defaultSettingsActions.copySetting', data); } - private _findSetting(filterResult: IFilterResult, key: string): { groupIdx: number, settingIdx: number, overallSettingIdx: number } | undefined { + private _findSetting(filterResult: IFilterResult, key: string): { groupIdx: number, settingIdx: number, overallSettingIdx: number; } | undefined { let overallSettingIdx = 0; for (let groupIdx = 0; groupIdx < filterResult.filteredGroups.length; groupIdx++) { @@ -826,11 +826,7 @@ class SideBySidePreferencesWidget extends Widget { this._register(attachStylerCallback(this.themeService, { scrollbarShadow }, colors => { const shadow = colors.scrollbarShadow ? colors.scrollbarShadow.toString() : null; - if (shadow) { - this.editablePreferencesEditorContainer.style.boxShadow = `-6px 0 5px -5px ${shadow}`; - } else { - this.editablePreferencesEditorContainer.style.boxShadow = null; - } + this.editablePreferencesEditorContainer.style.boxShadow = shadow ? `-6px 0 5px -5px ${shadow}` : ''; })); this.splitview.addView({ @@ -845,7 +841,7 @@ class SideBySidePreferencesWidget extends Widget { this._register(focusTracker.onDidFocus(() => this._onFocus.fire())); } - setInput(defaultPreferencesEditorInput: DefaultPreferencesEditorInput, editablePreferencesEditorInput: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<{ defaultPreferencesRenderer?: IPreferencesRenderer, editablePreferencesRenderer?: IPreferencesRenderer }> { + setInput(defaultPreferencesEditorInput: DefaultPreferencesEditorInput, editablePreferencesEditorInput: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<{ defaultPreferencesRenderer?: IPreferencesRenderer, editablePreferencesRenderer?: IPreferencesRenderer; }> { this.getOrCreateEditablePreferencesEditor(editablePreferencesEditorInput); this.settingsTargetsWidget.settingsTarget = this.getSettingsTarget(editablePreferencesEditorInput.getResource()!); return Promise.all([ @@ -990,10 +986,10 @@ export class DefaultPreferencesEditor extends BaseTextEditor { super(DefaultPreferencesEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, hostService); } - private static _getContributions(): IEditorContributionCtor[] { - const skipContributions = [FoldingController.prototype, SelectionHighlighter.prototype, FindController.prototype]; - const contributions = EditorExtensionsRegistry.getEditorContributions().filter(c => skipContributions.indexOf(c.prototype) === -1); - contributions.push(DefaultSettingsEditorContribution); + private static _getContributions(): IEditorContributionDescription[] { + const skipContributions = [FoldingController.ID, SelectionHighlighter.ID, FindController.ID]; + const contributions = EditorExtensionsRegistry.getEditorContributions().filter(c => skipContributions.indexOf(c.id) === -1); + contributions.push({ id: DefaultSettingsEditorContribution.ID, ctor: DefaultSettingsEditorContribution }); return contributions; } @@ -1049,20 +1045,25 @@ export class DefaultPreferencesEditor extends BaseTextEditor { return; } - this.getControl().setModel((editorModel).textEditorModel); + const editor = assertIsDefined(this.getControl()); + editor.setModel((editorModel).textEditorModel); })); } clearInput(): void { // Clear Model - this.getControl().setModel(null); + const editor = this.getControl(); + if (editor) { + editor.setModel(null); + } // Pass to super super.clearInput(); } layout(dimension: DOM.Dimension) { - this.getControl().layout(dimension); + const editor = assertIsDefined(this.getControl()); + editor.layout(dimension); } protected getAriaLabel(): string { @@ -1157,17 +1158,12 @@ abstract class AbstractSettingsEditorContribution extends Disposable implements } protected abstract _createPreferencesRenderer(): Promise | null> | null; - abstract getId(): string; } export class DefaultSettingsEditorContribution extends AbstractSettingsEditorContribution implements ISettingsEditorContribution { static readonly ID: string = 'editor.contrib.defaultsettings'; - getId(): string { - return DefaultSettingsEditorContribution.ID; - } - protected _createPreferencesRenderer(): Promise | null> | null { return this.preferencesService.createPreferencesEditorModel(this.editor.getModel()!.uri) .then(editorModel => { @@ -1194,10 +1190,6 @@ class SettingsEditorContribution extends AbstractSettingsEditorContribution impl this._register(this.workspaceContextService.onDidChangeWorkbenchState(() => this._onModelChanged())); } - getId(): string { - return SettingsEditorContribution.ID; - } - protected _createPreferencesRenderer(): Promise | null> | null { const model = this.editor.getModel(); if (model) { @@ -1227,4 +1219,4 @@ class SettingsEditorContribution extends AbstractSettingsEditorContribution impl } } -registerEditorContribution(SettingsEditorContribution); +registerEditorContribution(SettingsEditorContribution.ID, SettingsEditorContribution); diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index f80f7468de..e3b608b324 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -32,6 +32,7 @@ import { DefaultSettingsEditorModel, SettingsEditorModel, WorkspaceConfiguration import { IMarkerService, IMarkerData, MarkerSeverity, MarkerTag } from 'vs/platform/markers/common/markers'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { find } from 'vs/base/common/arrays'; export interface IPreferencesRenderer extends IDisposable { readonly preferencesModel: IPreferencesEditorModel; @@ -258,7 +259,7 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR this.settingsGroupTitleRenderer = this._register(instantiationService.createInstance(SettingsGroupTitleRenderer, editor)); this.filteredMatchesRenderer = this._register(instantiationService.createInstance(FilteredMatchesRenderer, editor)); this.editSettingActionRenderer = this._register(instantiationService.createInstance(EditSettingRenderer, editor, preferencesModel, this.settingHighlighter)); - this.bracesHidingRenderer = this._register(instantiationService.createInstance(BracesHidingRenderer, editor, preferencesModel)); + this.bracesHidingRenderer = this._register(instantiationService.createInstance(BracesHidingRenderer, editor)); this.hiddenAreasRenderer = this._register(instantiationService.createInstance(HiddenAreasRenderer, editor, [this.settingsGroupTitleRenderer, this.filteredMatchesRenderer, this.bracesHidingRenderer])); this._register(this.editSettingActionRenderer.onUpdateSetting(e => this._onUpdatePreference.fire(e))); @@ -321,12 +322,7 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR const { key, overrideOf } = setting; if (overrideOf) { const setting = this.getSetting(overrideOf); - for (const override of setting!.overrides!) { - if (override.key === key) { - return override; - } - } - return undefined; + return find(setting!.overrides!, override => override.key === key); } const settingsGroups = this.filterResult ? this.filterResult.filteredGroups : this.preferencesModel.settingsGroups; return this.getPreference(key, settingsGroups); diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index 3359bfee07..edcd5bb032 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -34,6 +34,7 @@ import { PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIV import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { isEqual } from 'vs/base/common/resources'; export class SettingsHeaderWidget extends Widget implements IViewZone { @@ -341,7 +342,7 @@ export class FolderSettingsActionViewItem extends BaseActionViewItem { this.container = container; this.labelElement = DOM.$('.action-title'); this.detailsElement = DOM.$('.action-details'); - this.dropDownElement = DOM.$('.dropdown-icon.octicon.octicon-triangle-down.hide'); + this.dropDownElement = DOM.$('.dropdown-icon.codicon.codicon-triangle-down.hide'); this.anchorElement = DOM.$('a.action-label.folder-settings', { role: 'button', 'aria-haspopup': 'true', @@ -387,7 +388,7 @@ export class FolderSettingsActionViewItem extends BaseActionViewItem { const oldFolder = this._folder; const workspace = this.contextService.getWorkspace(); if (oldFolder) { - this._folder = workspace.folders.filter(folder => folder.uri.toString() === oldFolder.uri.toString())[0] || workspace.folders[0]; + this._folder = workspace.folders.filter(folder => isEqual(folder.uri, oldFolder.uri))[0] || workspace.folders[0]; } this._folder = this._folder ? this._folder : workspace.folders.length === 1 ? workspace.folders[0] : null; @@ -440,7 +441,7 @@ export class FolderSettingsActionViewItem extends BaseActionViewItem { return { id: 'folderSettingsTarget' + index, label: this.labelWithCount(folder.name, folderCount), - checked: this.folder && this.folder.uri.toString() === folder.uri.toString(), + checked: this.folder && isEqual(this.folder.uri, folder.uri), enabled: true, run: () => this._action.run(folder) }; @@ -574,7 +575,7 @@ export class SettingsTargetsWidget extends Widget { const isSameTarget = this.settingsTarget === settingsTarget || settingsTarget instanceof URI && this.settingsTarget instanceof URI && - this.settingsTarget.toString() === settingsTarget.toString(); + isEqual(this.settingsTarget, settingsTarget); if (!isSameTarget) { this.settingsTarget = settingsTarget; @@ -632,13 +633,13 @@ export class SearchWidget extends Widget { if (this.options.showResultCount) { this.countElement = DOM.append(this.controlsDiv, DOM.$('.settings-count-widget')); this._register(attachStylerCallback(this.themeService, { badgeBackground, contrastBorder }, colors => { - const background = colors.badgeBackground ? colors.badgeBackground.toString() : null; - const border = colors.contrastBorder ? colors.contrastBorder.toString() : null; + const background = colors.badgeBackground ? colors.badgeBackground.toString() : ''; + const border = colors.contrastBorder ? colors.contrastBorder.toString() : ''; this.countElement.style.backgroundColor = background; - this.countElement.style.borderWidth = border ? '1px' : null; - this.countElement.style.borderStyle = border ? 'solid' : null; + this.countElement.style.borderWidth = border ? '1px' : ''; + this.countElement.style.borderStyle = border ? 'solid' : ''; this.countElement.style.borderColor = border; const color = this.themeService.getTheme().getColor(badgeForeground); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index ec83762222..4b421f45b9 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -12,7 +12,7 @@ import * as collections from 'vs/base/common/collections'; import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors'; import { Iterator } from 'vs/base/common/iterator'; import * as strings from 'vs/base/common/strings'; -import { isArray, withNullAsUndefined } from 'vs/base/common/types'; +import { isArray, withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/settingsEditor2'; import { localize } from 'vs/nls'; @@ -71,6 +71,7 @@ export class SettingsEditor2 extends BaseEditor { private static NUM_INSTANCES: number = 0; private static SETTING_UPDATE_FAST_DEBOUNCE: number = 200; private static SETTING_UPDATE_SLOW_DEBOUNCE: number = 1000; + private static CONFIG_SCHEMA_UPDATE_DELAYER = 500; private static readonly SUGGESTIONS: string[] = [ `@${MODIFIED_SETTING_TAG}`, '@tag:usesOnlineServices', `@${EXTENSION_SETTING_TAG}` @@ -115,6 +116,8 @@ export class SettingsEditor2 extends BaseEditor { private remoteSearchThrottle: ThrottledDelayer; private searchInProgress: CancellationTokenSource | null = null; + private updatedConfigSchemaDelayer: Delayer; + private settingFastUpdateDelayer: Delayer; private settingSlowUpdateDelayer: Delayer; private pendingSettingUpdate: { key: string, value: any } | null = null; @@ -161,6 +164,8 @@ export class SettingsEditor2 extends BaseEditor { this.settingFastUpdateDelayer = new Delayer(SettingsEditor2.SETTING_UPDATE_FAST_DEBOUNCE); this.settingSlowUpdateDelayer = new Delayer(SettingsEditor2.SETTING_UPDATE_SLOW_DEBOUNCE); + this.updatedConfigSchemaDelayer = new Delayer(SettingsEditor2.CONFIG_SCHEMA_UPDATE_DELAYER); + this.inSettingsEditorContextKey = CONTEXT_SETTINGS_EDITOR.bindTo(contextKeyService); this.searchFocusContextKey = CONTEXT_SETTINGS_SEARCH_FOCUS.bindTo(contextKeyService); this.tocRowFocused = CONTEXT_TOC_ROW_FOCUS.bindTo(contextKeyService); @@ -216,31 +221,29 @@ export class SettingsEditor2 extends BaseEditor { return super.setInput(input, options, token) .then(() => timeout(0)) // Force setInput to be async .then(() => { - return this.render(token); - }) - .then(() => { - options = options || SettingsEditorOptions.create({}); + // Don't block setInput on render (which can trigger an async search) + this.render(token).then(() => { + options = options || SettingsEditorOptions.create({}); - if (!this.viewState.settingsTarget) { - if (!options.target) { - options.target = ConfigurationTarget.USER_LOCAL; + if (!this.viewState.settingsTarget) { + if (!options.target) { + options.target = ConfigurationTarget.USER_LOCAL; + } } - } - this._setOptions(options); + this._setOptions(options); - this._register(input.onDispose(() => { - this.searchWidget.setValue(''); - })); + this._register(input.onDispose(() => { + this.searchWidget.setValue(''); + })); - // Init TOC selection - this.updateTreeScrollSync(); - - this.restoreCachedState(); + // Init TOC selection + this.updateTreeScrollSync(); + }); }); } - private restoreCachedState(): void { + private restoreCachedState(): ISettingsEditor2State | null { const cachedState = this.group && this.input && this.editorMemento.loadEditorState(this.group, this.input); if (cachedState && typeof cachedState.target === 'object') { cachedState.target = URI.revive(cachedState.target); @@ -249,9 +252,15 @@ export class SettingsEditor2 extends BaseEditor { if (cachedState) { const settingsTarget = cachedState.target; this.settingsTargetsWidget.settingsTarget = settingsTarget; - this.onDidSettingsTargetChange(settingsTarget); + this.viewState.settingsTarget = settingsTarget; this.searchWidget.setValue(cachedState.searchQuery); } + + if (this.input) { + this.editorMemento.clearEditorState(this.input, this.group); + } + + return withUndefinedAsNull(cachedState); } setOptions(options: SettingsEditorOptions | undefined): void { @@ -281,10 +290,6 @@ export class SettingsEditor2 extends BaseEditor { clearInput(): void { this.inSettingsEditorContextKey.set(false); - if (this.input) { - this.editorMemento.clearEditorState(this.input, this.group); - } - super.clearInput(); } @@ -408,15 +413,15 @@ export class SettingsEditor2 extends BaseEditor { this.countElement = DOM.append(searchContainer, DOM.$('.settings-count-widget')); this._register(attachStylerCallback(this.themeService, { badgeBackground, contrastBorder, badgeForeground }, colors => { - const background = colors.badgeBackground ? colors.badgeBackground.toString() : null; - const border = colors.contrastBorder ? colors.contrastBorder.toString() : null; - const foreground = colors.badgeForeground ? colors.badgeForeground.toString() : null; + const background = colors.badgeBackground ? colors.badgeBackground.toString() : ''; + const border = colors.contrastBorder ? colors.contrastBorder.toString() : ''; + const foreground = colors.badgeForeground ? colors.badgeForeground.toString() : ''; this.countElement.style.backgroundColor = background; this.countElement.style.color = foreground; - this.countElement.style.borderWidth = border ? '1px' : null; - this.countElement.style.borderStyle = border ? 'solid' : null; + this.countElement.style.borderWidth = border ? '1px' : ''; + this.countElement.style.borderStyle = border ? 'solid' : ''; this.countElement.style.borderColor = border; })); @@ -855,7 +860,11 @@ export class SettingsEditor2 extends BaseEditor { return undefined; } - this._register(model.onDidChangeGroups(() => this.onConfigUpdate())); + this._register(model.onDidChangeGroups(() => { + this.updatedConfigSchemaDelayer.trigger(() => { + this.onConfigUpdate(undefined, undefined, true); + }); + })); this.defaultSettingsEditorModel = model; return this.onConfigUpdate(undefined, true); }); @@ -889,7 +898,7 @@ export class SettingsEditor2 extends BaseEditor { }); } - private onConfigUpdate(keys?: string[], forceRefresh = false): void { + private async onConfigUpdate(keys?: string[], forceRefresh = false, schemaChange = false): Promise { if (keys && this.settingsTreeModel) { return this.updateElementsByKey(keys); } @@ -922,23 +931,26 @@ export class SettingsEditor2 extends BaseEditor { if (this.settingsTreeModel) { this.settingsTreeModel.update(resolvedSettingsRoot); - // Make sure that all extensions' settings are included in search results - const cachedState = this.group && this.input && this.editorMemento.loadEditorState(this.group, this.input); - if (cachedState && cachedState.searchQuery) { - this.triggerSearch(cachedState.searchQuery); - } else { - this.renderTree(undefined, forceRefresh); - this.refreshTOCTree(); + if (schemaChange && !!this.searchResultModel) { + // If an extension's settings were just loaded and a search is active, retrigger the search so it shows up + return await this.onSearchInputChanged(); } + + this.refreshTOCTree(); + this.renderTree(undefined, forceRefresh); } else { this.settingsTreeModel = this.instantiationService.createInstance(SettingsTreeModel, this.viewState); this.settingsTreeModel.update(resolvedSettingsRoot); this.tocTreeModel.settingsTreeRoot = this.settingsTreeModel.root as SettingsTreeGroupElement; - this.refreshTOCTree(); - this.refreshTree(); - - this.tocTree.collapseAll(); + const cachedState = this.restoreCachedState(); + if (cachedState && cachedState.searchQuery) { + await this.onSearchInputChanged(); + } else { + this.refreshTOCTree(); + this.refreshTree(); + this.tocTree.collapseAll(); + } } } @@ -1045,14 +1057,14 @@ export class SettingsEditor2 extends BaseEditor { } } - private onSearchInputChanged(): void { + private async onSearchInputChanged(): Promise { const query = this.searchWidget.getValue().trim(); this.delayedFilterLogging.cancel(); - this.triggerSearch(query.replace(/›/g, ' ')).then(() => { - if (query && this.searchResultModel) { - this.delayedFilterLogging.trigger(() => this.reportFilteringUsed(query, this.searchResultModel!.getUniqueResults())); - } - }); + await this.triggerSearch(query.replace(/›/g, ' ')); + + if (query && this.searchResultModel) { + this.delayedFilterLogging.trigger(() => this.reportFilteringUsed(query, this.searchResultModel!.getUniqueResults())); + } } private parseSettingFromJSON(query: string): string | null { @@ -1097,16 +1109,16 @@ export class SettingsEditor2 extends BaseEditor { // Added a filter model this.tocTree.setSelection([]); this.tocTree.expandAll(); + this.refreshTOCTree(); this.renderResultCountMessages(); this.refreshTree(); } else { // Leaving search mode this.tocTree.collapseAll(); + this.refreshTOCTree(); this.renderResultCountMessages(); this.refreshTree(); } - - this.refreshTOCTree(); } return Promise.resolve(); @@ -1235,8 +1247,8 @@ export class SettingsEditor2 extends BaseEditor { this.viewState.filterToCategory = undefined; this.tocTree.expandAll(); - this.renderTree(undefined, true); this.refreshTOCTree(); + this.renderTree(undefined, true); return result; }); } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index 8d63a134b2..a10b2cac51 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -132,7 +132,7 @@ export const tocData: ITOCEntry = { { id: 'features/search', label: localize('search', "Search"), - settings: ['search.*', 'searchRipgrep.*'] + settings: ['search.*'] } , { @@ -155,6 +155,11 @@ export const tocData: ITOCEntry = { label: localize('terminal', "Terminal"), settings: ['terminal.*'] }, + { + id: 'features/task', + label: localize('task', "Task"), + settings: ['task.*'] + }, { id: 'features/problems', label: localize('problems', "Problems"), diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index e4cc6ce58d..cecbbf2ff3 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -12,7 +12,7 @@ import { alert as ariaAlert } from 'vs/base/browser/ui/aria/aria'; import { Button } from 'vs/base/browser/ui/button/button'; import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; -import { IListVirtualDelegate, ListAriaRootRole } from 'vs/base/browser/ui/list/list'; +import { ListAriaRootRole, CachedListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { DefaultStyleController } from 'vs/base/browser/ui/list/listWidget'; import { ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; @@ -1379,29 +1379,7 @@ export class SettingsTreeFilter implements ITreeFilter { } } -class SettingsTreeDelegate implements IListVirtualDelegate { - - private heightCache = new WeakMap(); - - getHeight(element: SettingsTreeGroupChild): number { - const cachedHeight = this.heightCache.get(element); - - if (typeof cachedHeight === 'number') { - return cachedHeight; - } - - if (element instanceof SettingsTreeGroupElement) { - if (element.isFirstGroup) { - return 31; - } - - return 40 + (7 * element.level); - } - - return element instanceof SettingsTreeSettingElement && element.valueType === SettingValueType.Boolean ? - 78 : - 104; - } +class SettingsTreeDelegate extends CachedListVirtualDelegate { getTemplateId(element: SettingsTreeGroupElement | SettingsTreeSettingElement | SettingsTreeNewExtensionsElement): string { if (element instanceof SettingsTreeGroupElement) { @@ -1447,8 +1425,16 @@ class SettingsTreeDelegate implements IListVirtualDelegate { filter: instantiationService.createInstance(SettingsTreeFilter, viewState) }); - this.disposables = []; - this.disposables.push(registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { + this.disposables.clear(); + this.disposables.add(registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const activeBorderColor = theme.getColor(focusBorder); if (activeBorderColor) { // TODO@rob - why isn't this applied when added to the stylesheet from tocTree.ts? Seems like a chromium glitch. @@ -1539,7 +1525,7 @@ export class SettingsTree extends ObjectTree { this.getHTMLElement().classList.add(treeClass); - this.disposables.push(attachStyler(themeService, { + this.disposables.add(attachStyler(themeService, { listActiveSelectionBackground: transparent(Color.white, 0), listActiveSelectionForeground: foreground, listFocusAndSelectionBackground: transparent(Color.white, 0), diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index c4aba3e07b..79d9dfe978 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -90,7 +90,7 @@ export class SettingsTreeNewExtensionsElement extends SettingsTreeElement { } export class SettingsTreeSettingElement extends SettingsTreeElement { - private static MAX_DESC_LINES = 20; + private static readonly MAX_DESC_LINES = 20; setting: ISetting; @@ -273,13 +273,7 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { return false; } - for (let extensionId of extensionFilters) { - if (extensionId.toLowerCase() === this.setting.extensionInfo.id.toLowerCase()) { - return true; - } - } - - return false; + return Array.from(extensionFilters).some(extensionId => extensionId.toLowerCase() === this.setting.extensionInfo!.id.toLowerCase()); } } @@ -409,7 +403,7 @@ function sanitizeId(id: string): string { return id.replace(/[\.\/]/, '_'); } -export function settingKeyToDisplayFormat(key: string, groupId = ''): { category: string, label: string } { +export function settingKeyToDisplayFormat(key: string, groupId = ''): { category: string, label: string; } { const lastDotIdx = key.lastIndexOf('.'); let category = ''; if (lastDotIdx >= 0) { @@ -542,12 +536,18 @@ export class SearchResultModel extends SettingsTreeModel { setResult(order: SearchResultIdx, result: ISearchResult | null): void { this.cachedUniqueSearchResults = null; + this.newExtensionSearchResults = null; + this.rawSearchResults = this.rawSearchResults || []; if (!result) { delete this.rawSearchResults[order]; return; } + if (result.exactMatch) { + this.rawSearchResults = []; + } + this.rawSearchResults[order] = result; this.updateChildren(); } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts index ef02d586a1..a92878548b 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts @@ -57,6 +57,11 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-value-checkbox { background-color: ${checkboxBackgroundColor} !important; }`); } + const checkboxForegroundColor = theme.getColor(settingsCheckboxForeground); + if (checkboxForegroundColor) { + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-value-checkbox { color: ${checkboxForegroundColor} !important; }`); + } + const checkboxBorderColor = theme.getColor(settingsCheckboxBorder); if (checkboxBorderColor) { collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-value-checkbox { border-color: ${checkboxBorderColor} !important; }`); diff --git a/src/vs/workbench/contrib/preferences/browser/tocTree.ts b/src/vs/workbench/contrib/preferences/browser/tocTree.ts index ec9f429839..4f22c431e6 100644 --- a/src/vs/workbench/contrib/preferences/browser/tocTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/tocTree.ts @@ -211,7 +211,7 @@ export class TOCTree extends ObjectTree { this.getHTMLElement().classList.add(treeClass); - this.disposables.push(attachStyler(themeService, { + this.disposables.add(attachStyler(themeService, { listActiveSelectionBackground: editorBackground, listActiveSelectionForeground: settingsHeaderForeground, listFocusAndSelectionBackground: editorBackground, diff --git a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts index 5576a1ca6b..997f8f0fc2 100644 --- a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts @@ -3,9 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; -import * as arrays from 'vs/base/common/arrays'; -import * as types from 'vs/base/common/types'; +import { localize } from 'vs/nls'; +import { distinct } from 'vs/base/common/arrays'; +import { withNullAsUndefined, isFunction } from 'vs/base/common/types'; import { Language } from 'vs/base/common/platform'; import { Action, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { Mode, IEntryRunContext, IAutoFocus, IModel, IQuickNavigateConfiguration } from 'vs/base/parts/quickopen/common/quickOpen'; @@ -35,26 +35,11 @@ import { timeout } from 'vs/base/common/async'; export const ALL_COMMANDS_PREFIX = '>'; -let lastCommandPaletteInput: string; -let commandHistory: LRUCache; -let commandCounter = 1; - interface ISerializedCommandHistory { usesLRU?: boolean; entries: { key: string; value: number }[]; } -function resolveCommandHistory(configurationService: IConfigurationService): number { - const config = configurationService.getValue(); - - let commandHistory = config.workbench && config.workbench.commandPalette && config.workbench.commandPalette.history; - if (typeof commandHistory !== 'number') { - commandHistory = CommandsHistory.DEFAULT_COMMANDS_HISTORY_LENGTH; - } - - return commandHistory; -} - class CommandsHistory extends Disposable { static readonly DEFAULT_COMMANDS_HISTORY_LENGTH = 50; @@ -62,7 +47,10 @@ class CommandsHistory extends Disposable { private static readonly PREF_KEY_CACHE = 'commandPalette.mru.cache'; private static readonly PREF_KEY_COUNTER = 'commandPalette.mru.counter'; - private commandHistoryLength = 0; + private static cache: LRUCache | undefined; + private static counter = 1; + + private configuredCommandsHistoryLength = 0; constructor( @IStorageService private readonly storageService: IStorageService, @@ -81,10 +69,10 @@ class CommandsHistory extends Disposable { } private updateConfiguration(): void { - this.commandHistoryLength = resolveCommandHistory(this.configurationService); + this.configuredCommandsHistoryLength = CommandsHistory.getConfiguredCommandHistoryLength(this.configurationService); - if (commandHistory && commandHistory.limit !== this.commandHistoryLength) { - commandHistory.limit = this.commandHistoryLength; + if (CommandsHistory.cache && CommandsHistory.cache.limit !== this.configuredCommandsHistoryLength) { + CommandsHistory.cache.limit = this.configuredCommandsHistoryLength; CommandsHistory.saveState(this.storageService); } @@ -101,7 +89,7 @@ class CommandsHistory extends Disposable { } } - commandHistory = new LRUCache(this.commandHistoryLength, 1); + const cache = CommandsHistory.cache = new LRUCache(this.configuredCommandsHistoryLength, 1); if (serializedCache) { let entries: { key: string; value: number }[]; if (serializedCache.usesLRU) { @@ -109,35 +97,64 @@ class CommandsHistory extends Disposable { } else { entries = serializedCache.entries.sort((a, b) => a.value - b.value); } - entries.forEach(entry => commandHistory.set(entry.key, entry.value)); + entries.forEach(entry => cache.set(entry.key, entry.value)); } - commandCounter = this.storageService.getNumber(CommandsHistory.PREF_KEY_COUNTER, StorageScope.GLOBAL, commandCounter); + CommandsHistory.counter = this.storageService.getNumber(CommandsHistory.PREF_KEY_COUNTER, StorageScope.GLOBAL, CommandsHistory.counter); } push(commandId: string): void { - commandHistory.set(commandId, commandCounter++); // set counter to command + if (!CommandsHistory.cache) { + return; + } + + CommandsHistory.cache.set(commandId, CommandsHistory.counter++); // set counter to command CommandsHistory.saveState(this.storageService); } peek(commandId: string): number | undefined { - return commandHistory.peek(commandId); + return CommandsHistory.cache?.peek(commandId); } static saveState(storageService: IStorageService): void { + if (!CommandsHistory.cache) { + return; + } + const serializedCache: ISerializedCommandHistory = { usesLRU: true, entries: [] }; - commandHistory.forEach((value, key) => serializedCache.entries.push({ key, value })); + CommandsHistory.cache.forEach((value, key) => serializedCache.entries.push({ key, value })); storageService.store(CommandsHistory.PREF_KEY_CACHE, JSON.stringify(serializedCache), StorageScope.GLOBAL); - storageService.store(CommandsHistory.PREF_KEY_COUNTER, commandCounter, StorageScope.GLOBAL); + storageService.store(CommandsHistory.PREF_KEY_COUNTER, CommandsHistory.counter, StorageScope.GLOBAL); + } + + static getConfiguredCommandHistoryLength(configurationService: IConfigurationService): number { + const config = configurationService.getValue(); + + const configuredCommandHistoryLength = config.workbench?.commandPalette?.history; + if (typeof configuredCommandHistoryLength === 'number') { + return configuredCommandHistoryLength; + } + + return CommandsHistory.DEFAULT_COMMANDS_HISTORY_LENGTH; + } + + static clearHistory(configurationService: IConfigurationService, storageService: IStorageService): void { + const commandHistoryLength = CommandsHistory.getConfiguredCommandHistoryLength(configurationService); + CommandsHistory.cache = new LRUCache(commandHistoryLength); + CommandsHistory.counter = 1; + + CommandsHistory.saveState(storageService); } } +let lastCommandPaletteInput: string | undefined = undefined; + export class ShowAllCommandsAction extends Action { static readonly ID = 'workbench.action.showCommands'; - static readonly LABEL = nls.localize('showTriggerActions', "Show All Commands"); + static readonly LABEL = localize('showTriggerActions', "Show All Commands"); constructor( id: string, @@ -150,7 +167,7 @@ export class ShowAllCommandsAction extends Action { run(): Promise { const config = this.configurationService.getValue(); - const restoreInput = config.workbench && config.workbench.commandPalette && config.workbench.commandPalette.preserveInput === true; + const restoreInput = config.workbench?.commandPalette?.preserveInput === true; // Show with last command palette input if any and configured let value = ALL_COMMANDS_PREFIX; @@ -167,7 +184,7 @@ export class ShowAllCommandsAction extends Action { export class ClearCommandHistoryAction extends Action { static readonly ID = 'workbench.action.clearCommandHistory'; - static readonly LABEL = nls.localize('clearCommandHistory', "Clear Command History"); + static readonly LABEL = localize('clearCommandHistory', "Clear Command History"); constructor( id: string, @@ -179,12 +196,9 @@ export class ClearCommandHistoryAction extends Action { } run(): Promise { - const commandHistoryLength = resolveCommandHistory(this.configurationService); + const commandHistoryLength = CommandsHistory.getConfiguredCommandHistoryLength(this.configurationService); if (commandHistoryLength > 0) { - commandHistory = new LRUCache(commandHistoryLength); - commandCounter = 1; - - CommandsHistory.saveState(this.storageService); + CommandsHistory.clearHistory(this.configurationService, this.storageService); } return Promise.resolve(undefined); @@ -196,7 +210,7 @@ class CommandPaletteEditorAction extends EditorAction { constructor() { super({ id: ShowAllCommandsAction.ID, - label: nls.localize('showCommands.label', "Command Palette..."), + label: localize('showCommands.label', "Command Palette..."), alias: 'Command Palette', precondition: undefined, menuOpts: { @@ -224,10 +238,10 @@ abstract class BaseCommandEntry extends QuickOpenEntryGroup { constructor( private commandId: string, - private keybinding: ResolvedKeybinding, + private keybinding: ResolvedKeybinding | undefined, private label: string, - alias: string, - highlights: { label: IHighlight[], alias?: IHighlight[] }, + alias: string | undefined, + highlights: { label: IHighlight[] | null, alias: IHighlight[] | null }, private onBeforeRun: (commandId: string) => void, @INotificationService private readonly notificationService: INotificationService, @ITelemetryService protected telemetryService: ITelemetryService @@ -240,10 +254,10 @@ abstract class BaseCommandEntry extends QuickOpenEntryGroup { if (this.label !== alias) { this.alias = alias; } else { - highlights.alias = undefined; + highlights.alias = null; } - this.setHighlights(highlights.label, undefined, highlights.alias); + this.setHighlights(withNullAsUndefined(highlights.label), undefined, withNullAsUndefined(highlights.alias)); } getCommandId(): string { @@ -266,7 +280,7 @@ abstract class BaseCommandEntry extends QuickOpenEntryGroup { this.description = description; } - getKeybinding(): ResolvedKeybinding { + getKeybinding(): ResolvedKeybinding | undefined { return this.keybinding; } @@ -276,10 +290,10 @@ abstract class BaseCommandEntry extends QuickOpenEntryGroup { getAriaLabel(): string { if (this.keybindingAriaLabel) { - return nls.localize('entryAriaLabelWithKey', "{0}, {1}, commands", this.getLabel(), this.keybindingAriaLabel); + return localize('entryAriaLabelWithKey', "{0}, {1}, commands", this.getLabel(), this.keybindingAriaLabel); } - return nls.localize('entryAriaLabel', "{0}, commands", this.getLabel()); + return localize('entryAriaLabel', "{0}, commands", this.getLabel()); } run(mode: Mode, context: IEntryRunContext): boolean { @@ -319,7 +333,7 @@ abstract class BaseCommandEntry extends QuickOpenEntryGroup { this.onError(error); } } else { - this.notificationService.info(nls.localize('actionNotEnabled', "Command '{0}' is not enabled in the current context.", this.getLabel())); + this.notificationService.info(localize('actionNotEnabled', "Command '{0}' is not enabled in the current context.", this.getLabel())); } }, 50); } @@ -329,7 +343,7 @@ abstract class BaseCommandEntry extends QuickOpenEntryGroup { return; } - this.notificationService.error(error || nls.localize('canNotRun', "Command '{0}' resulted in an error.", this.label)); + this.notificationService.error(error || localize('canNotRun', "Command '{0}' resulted in an error.", this.label)); } } @@ -337,10 +351,10 @@ class EditorActionCommandEntry extends BaseCommandEntry { constructor( commandId: string, - keybinding: ResolvedKeybinding, + keybinding: ResolvedKeybinding | undefined, label: string, - meta: string, - highlights: { label: IHighlight[], alias: IHighlight[] }, + meta: string | undefined, + highlights: { label: IHighlight[] | null, alias: IHighlight[] | null }, private action: IEditorAction, onBeforeRun: (commandId: string) => void, @INotificationService notificationService: INotificationService, @@ -358,10 +372,10 @@ class ActionCommandEntry extends BaseCommandEntry { constructor( commandId: string, - keybinding: ResolvedKeybinding, + keybinding: ResolvedKeybinding | undefined, label: string, - alias: string, - highlights: { label: IHighlight[], alias: IHighlight[] }, + alias: string | undefined, + highlights: { label: IHighlight[] | null, alias: IHighlight[] | null }, private action: Action, onBeforeRun: (commandId: string) => void, @INotificationService notificationService: INotificationService, @@ -408,7 +422,7 @@ export class CommandsHandler extends QuickOpenHandler implements IDisposable { } private updateConfiguration(): void { - this.commandHistoryEnabled = resolveCommandHistory(this.configurationService) > 0; + this.commandHistoryEnabled = CommandsHistory.getConfiguredCommandHistoryLength(this.configurationService) > 0; } async getResults(searchValue: string, token: CancellationToken): Promise { @@ -439,7 +453,7 @@ export class CommandsHandler extends QuickOpenHandler implements IDisposable { // Editor Actions const activeTextEditorWidget = this.editorService.activeTextEditorWidget; let editorActions: IEditorAction[] = []; - if (activeTextEditorWidget && types.isFunction(activeTextEditorWidget.getSupportedActions)) { + if (activeTextEditorWidget && isFunction(activeTextEditorWidget.getSupportedActions)) { editorActions = activeTextEditorWidget.getSupportedActions(); } @@ -456,7 +470,7 @@ export class CommandsHandler extends QuickOpenHandler implements IDisposable { let entries = [...editorEntries, ...commandEntries]; // Remove duplicates - entries = arrays.distinct(entries, entry => `${entry.getLabel()}${entry.getGroupLabel()}${entry.getCommandId()}`); + entries = distinct(entries, entry => `${entry.getLabel()}${entry.getGroupLabel()}${entry.getCommandId()}`); // Handle label clashes const commandLabels = new Set(); @@ -494,12 +508,12 @@ export class CommandsHandler extends QuickOpenHandler implements IDisposable { // only if we have recently used commands in the result set const firstEntry = entries[0]; if (firstEntry && this.commandsHistory.peek(firstEntry.getCommandId())) { - firstEntry.setGroupLabel(nls.localize('recentlyUsed', "recently used")); + firstEntry.setGroupLabel(localize('recentlyUsed', "recently used")); for (let i = 1; i < entries.length; i++) { const entry = entries[i]; if (!this.commandsHistory.peek(entry.getCommandId())) { entry.setShowBorder(true); - entry.setGroupLabel(nls.localize('morecCommands', "other commands")); + entry.setGroupLabel(localize('morecCommands', "other commands")); break; } } @@ -520,7 +534,7 @@ export class CommandsHandler extends QuickOpenHandler implements IDisposable { if (label) { // Alias for non default languages - const alias = !Language.isDefaultVariant() ? action.alias : null; + const alias = !Language.isDefaultVariant() ? action.alias : undefined; const labelHighlights = wordFilter(searchValue, label); const aliasHighlights = alias ? wordFilter(searchValue, alias) : null; @@ -547,15 +561,15 @@ export class CommandsHandler extends QuickOpenHandler implements IDisposable { let category, label = title; if (action.item.category) { category = typeof action.item.category === 'string' ? action.item.category : action.item.category.value; - label = nls.localize('cat.title', "{0}: {1}", category, title); + label = localize('cat.title', "{0}: {1}", category, title); } if (label) { const labelHighlights = wordFilter(searchValue, label); // Add an 'alias' in original language when running in different locale - const aliasTitle = (!Language.isDefaultVariant() && typeof action.item.title !== 'string') ? action.item.title.original : null; - const aliasCategory = (!Language.isDefaultVariant() && category && action.item.category && typeof action.item.category !== 'string') ? action.item.category.original : null; + const aliasTitle = (!Language.isDefaultVariant() && typeof action.item.title !== 'string') ? action.item.title.original : undefined; + const aliasCategory = (!Language.isDefaultVariant() && category && action.item.category && typeof action.item.category !== 'string') ? action.item.category.original : undefined; let alias; if (aliasTitle && category) { alias = aliasCategory ? `${aliasCategory}: ${aliasTitle}` : `${category}: ${aliasTitle}`; @@ -590,7 +604,7 @@ export class CommandsHandler extends QuickOpenHandler implements IDisposable { } getEmptyLabel(searchString: string): string { - return nls.localize('noCommandsMatching', "No commands matching"); + return localize('noCommandsMatching', "No commands matching"); } onClose(canceled: boolean): void { diff --git a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts index 62c94d58ae..910b7a5951 100644 --- a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts @@ -16,7 +16,7 @@ import { IModelDecorationsChangeAccessor, OverviewRulerLane, IModelDeltaDecorati import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { getDocumentSymbols } from 'vs/editor/contrib/quickOpen/quickOpen'; -import { DocumentSymbolProviderRegistry, DocumentSymbol, symbolKindToCssClass, SymbolKind, SymbolTag } from 'vs/editor/common/modes'; +import { DocumentSymbolProviderRegistry, DocumentSymbol, SymbolKinds, SymbolKind, SymbolTag } from 'vs/editor/common/modes'; import { IRange, Range } from 'vs/editor/common/core/range'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { overviewRulerRangeHighlight } from 'vs/editor/common/view/editorColorRegistry'; @@ -195,7 +195,7 @@ class SymbolEntry extends EditorQuickOpenEntryGroup { return this.deprecated ? { extraClasses: ['deprecated'] } : undefined; } - getHighlights(): [IHighlight[], IHighlight[] | undefined, IHighlight[] | undefined] { + getHighlights(): [IHighlight[] | undefined, IHighlight[] | undefined, IHighlight[] | undefined] { return [ this.deprecated ? [] : filters.createMatches(this.score), undefined, @@ -425,7 +425,7 @@ export class GotoSymbolHandler extends QuickOpenHandler { // Show parent scope as description const description = element.containerName || ''; - const icon = symbolKindToCssClass(element.kind); + const icon = SymbolKinds.toCssClassName(element.kind); // Add results.push(new SymbolEntry(i, diff --git a/src/vs/workbench/contrib/quickopen/browser/helpHandler.ts b/src/vs/workbench/contrib/quickopen/browser/helpHandler.ts index 8f945c58ad..17ac7cb6df 100644 --- a/src/vs/workbench/contrib/quickopen/browser/helpHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/helpHandler.ts @@ -17,11 +17,11 @@ export const HELP_PREFIX = '?'; class HelpEntry extends QuickOpenEntryGroup { private prefixLabel: string; private prefix: string; - private description: string; + private description: string | undefined; private quickOpenService: IQuickOpenService; private openOnPreview: boolean; - constructor(prefix: string, description: string, quickOpenService: IQuickOpenService, openOnPreview: boolean) { + constructor(prefix: string, description: string | undefined, openOnPreview: boolean, quickOpenService: IQuickOpenService) { super(); if (!prefix) { @@ -44,7 +44,7 @@ class HelpEntry extends QuickOpenEntryGroup { return nls.localize('entryAriaLabel', "{0}, picker help", this.getLabel()); } - getDescription(): string { + getDescription(): string | undefined { return this.description; } @@ -101,9 +101,9 @@ export class HelpHandler extends QuickOpenHandler { matchingHandlers.forEach(handler => { if (handler instanceof QuickOpenHandlerDescriptor) { - workbenchScoped.push(new HelpEntry(handler.prefix, handler.description, this.quickOpenService, matchingHandlers.length === 1)); + workbenchScoped.push(new HelpEntry(handler.prefix, handler.description, matchingHandlers.length === 1, this.quickOpenService)); } else { - const entry = new HelpEntry(handler.prefix, handler.description, this.quickOpenService, matchingHandlers.length === 1); + const entry = new HelpEntry(handler.prefix, handler.description, matchingHandlers.length === 1, this.quickOpenService); if (handler.needsEditor) { editorScoped.push(entry); } else { diff --git a/src/vs/workbench/contrib/quickopen/browser/quickopen.contribution.ts b/src/vs/workbench/contrib/quickopen/browser/quickopen.contribution.ts index a18adf4cad..06099c31f9 100644 --- a/src/vs/workbench/contrib/quickopen/browser/quickopen.contribution.ts +++ b/src/vs/workbench/contrib/quickopen/browser/quickopen.contribution.ts @@ -193,4 +193,4 @@ MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { title: nls.localize('commandPalette', "Command Palette...") }, order: 1 -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts b/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts index e083ec6351..dbafc0e718 100644 --- a/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts @@ -196,7 +196,7 @@ export class ViewPickerHandler extends QuickOpenHandler { private hasToShowViewlet(viewlet: ViewletDescriptor): boolean { const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).get(viewlet.id); - if (viewContainer && viewContainer.hideIfEmpty) { + if (viewContainer?.hideIfEmpty) { const viewsCollection = this.viewsService.getViewDescriptors(viewContainer); return !!viewsCollection && viewsCollection.activeViewDescriptors.length > 0; } diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index c9fda273b2..aea45f60e9 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -57,13 +57,13 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo let changed = false; // Tree horizontal scrolling support - if (config.workbench && config.workbench.list && typeof config.workbench.list.horizontalScrolling === 'boolean' && config.workbench.list.horizontalScrolling !== this.treeHorizontalScrolling) { + if (typeof config.workbench?.list?.horizontalScrolling === 'boolean' && config.workbench.list.horizontalScrolling !== this.treeHorizontalScrolling) { this.treeHorizontalScrolling = config.workbench.list.horizontalScrolling; changed = true; } // Debug console word wrap - if (config.debug && typeof config.debug.console.wordWrap === 'boolean' && config.debug.console.wordWrap !== this.debugConsoleWordWrap) { + if (typeof config.debug?.console.wordWrap === 'boolean' && config.debug.console.wordWrap !== this.debugConsoleWordWrap) { this.debugConsoleWordWrap = config.debug.console.wordWrap; changed = true; } @@ -71,44 +71,44 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo if (isNative) { // Titlebar style - if (config.window && config.window.titleBarStyle !== this.titleBarStyle && (config.window.titleBarStyle === 'native' || config.window.titleBarStyle === 'custom')) { + if (typeof config.window?.titleBarStyle === 'string' && config.window?.titleBarStyle !== this.titleBarStyle && (config.window.titleBarStyle === 'native' || config.window.titleBarStyle === 'custom')) { this.titleBarStyle = config.window.titleBarStyle; changed = true; } // macOS: Native tabs - if (isMacintosh && config.window && typeof config.window.nativeTabs === 'boolean' && config.window.nativeTabs !== this.nativeTabs) { + if (isMacintosh && typeof config.window?.nativeTabs === 'boolean' && config.window.nativeTabs !== this.nativeTabs) { this.nativeTabs = config.window.nativeTabs; changed = true; } // macOS: Native fullscreen - if (isMacintosh && config.window && typeof config.window.nativeFullScreen === 'boolean' && config.window.nativeFullScreen !== this.nativeFullScreen) { + if (isMacintosh && typeof config.window?.nativeFullScreen === 'boolean' && config.window.nativeFullScreen !== this.nativeFullScreen) { this.nativeFullScreen = config.window.nativeFullScreen; changed = true; } // macOS: Click through (accept first mouse) - if (isMacintosh && config.window && typeof config.window.clickThroughInactive === 'boolean' && config.window.clickThroughInactive !== this.clickThroughInactive) { + if (isMacintosh && typeof config.window?.clickThroughInactive === 'boolean' && config.window.clickThroughInactive !== this.clickThroughInactive) { this.clickThroughInactive = config.window.clickThroughInactive; changed = true; } // Update channel - if (config.update && typeof config.update.mode === 'string' && config.update.mode !== this.updateMode) { + if (typeof config.update?.mode === 'string' && config.update.mode !== this.updateMode) { this.updateMode = config.update.mode; changed = true; } // Crash reporter - if (config.telemetry && typeof config.telemetry.enableCrashReporter === 'boolean' && config.telemetry.enableCrashReporter !== this.enableCrashReporter) { + if (typeof config.telemetry?.enableCrashReporter === 'boolean' && config.telemetry.enableCrashReporter !== this.enableCrashReporter) { this.enableCrashReporter = config.telemetry.enableCrashReporter; changed = true; } } // Configuration Sync Auth - if (config.configurationSync && typeof config.configurationSync.enableAuth === 'boolean' && config.configurationSync.enableAuth !== this.enableConfigSyncAuth) { + if (typeof config.configurationSync?.enableAuth === 'boolean' && config.configurationSync.enableAuth !== this.enableConfigSyncAuth) { this.enableConfigSyncAuth = config.configurationSync.enableAuth; changed = true; } @@ -130,18 +130,12 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo } } - private doConfirm(message: string, detail: string, primaryButton: string, confirmed: () => void): void { + private async doConfirm(message: string, detail: string, primaryButton: string, confirmed: () => void): Promise { if (this.hostService.hasFocus) { - this.dialogService.confirm({ - type: 'info', - message, - detail, - primaryButton - }).then(res => { - if (res.confirmed) { - confirmed(); - } - }); + const res = await this.dialogService.confirm({ type: 'info', message, detail, primaryButton }); + if (res.confirmed) { + confirmed(); + } } } } diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 0ca3bb4275..f8df07e692 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -433,8 +433,10 @@ export class RemoteViewlet extends ViewContainerViewlet implements IViewModel { } const descriptorAuthority = descriptor.viewDescriptor.remoteAuthority; - if (typeof descriptorAuthority === 'undefined' || descriptor.viewDescriptor.id === HelpPanel.ID) { + if (typeof descriptorAuthority === 'undefined') { panel.setExpanded(true); + } else if (descriptor.viewDescriptor.id === HelpPanel.ID) { + // Do nothing, keep the default behavior for Help } else { const descriptorAuthorityArr = Array.isArray(descriptorAuthority) ? descriptorAuthority : [descriptorAuthority]; if (descriptorAuthorityArr.indexOf(actualRemoteAuthority) >= 0) { @@ -466,7 +468,7 @@ Registry.as(ViewletExtensions.Viewlets).registerViewlet(new Vie class OpenRemoteViewletAction extends ShowViewletAction { static readonly ID = VIEWLET_ID; - static LABEL = nls.localize('toggleRemoteViewlet', "Show Remote Explorer"); + static readonly LABEL = nls.localize('toggleRemoteViewlet', "Show Remote Explorer"); constructor(id: string, label: string, @IViewletService viewletService: IViewletService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService) { super(id, label, VIEWLET_ID, viewletService, editorGroupService, layoutService); diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index cae326a28a..9a546f4651 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -17,7 +17,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { URI } from 'vs/base/common/uri'; -import { ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; +import { ISCMService, ISCMRepository, ISCMProvider } from 'vs/workbench/contrib/scm/common/scm'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { registerThemingParticipant, ITheme, ICssStyleCollector, themeColorFromId, IThemeService } from 'vs/platform/theme/common/themeService'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; @@ -37,7 +37,7 @@ import { IDiffEditorOptions, EditorOption } from 'vs/editor/common/config/editor import { Action, IAction, ActionRunner } from 'vs/base/common/actions'; import { IActionBarOptions, ActionsOrientation, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { basename } from 'vs/base/common/resources'; +import { basename, isEqualOrParent } from 'vs/base/common/resources'; import { MenuId, IMenuService, IMenu, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IChange, IEditorModel, ScrollType, IEditorContribution, IDiffEditorModel } from 'vs/editor/common/editorCommon'; @@ -149,7 +149,7 @@ function getChangeTypeColor(theme: ITheme, changeType: ChangeType): Color | unde } } -function getOuterEditorFromDiffEditor(accessor: ServicesAccessor): ICodeEditor | undefined { +function getOuterEditorFromDiffEditor(accessor: ServicesAccessor): ICodeEditor | null { const diffEditors = accessor.get(ICodeEditorService).listDiffEditors(); for (const diffEditor of diffEditors) { @@ -163,11 +163,11 @@ function getOuterEditorFromDiffEditor(accessor: ServicesAccessor): ICodeEditor | class DirtyDiffWidget extends PeekViewWidget { - private diffEditor: EmbeddedDiffEditorWidget; + private diffEditor!: EmbeddedDiffEditorWidget; private title: string; private menu: IMenu; - private index: number; - private change: IChange; + private index: number = 0; + private change: IChange | undefined; private height: number | undefined = undefined; private contextKeyService: IContextKeyService; @@ -320,7 +320,7 @@ class DirtyDiffWidget extends PeekViewWidget { super._doLayoutBody(height, width); this.diffEditor.layout({ height, width }); - if (typeof this.height === 'undefined') { + if (typeof this.height === 'undefined' && this.change) { this.revealChange(this.change); } @@ -556,7 +556,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ export class DirtyDiffController extends Disposable implements IEditorContribution { - private static readonly ID = 'editor.contrib.dirtydiff'; + public static readonly ID = 'editor.contrib.dirtydiff'; static get(editor: ICodeEditor): DirtyDiffController { return editor.getContribution(DirtyDiffController.ID); @@ -567,7 +567,7 @@ export class DirtyDiffController extends Disposable implements IEditorContributi private model: DirtyDiffModel | null = null; private widget: DirtyDiffWidget | null = null; private currentIndex: number = -1; - private readonly isDirtyDiffVisible: IContextKey; + private readonly isDirtyDiffVisible!: IContextKey; private session: IDisposable = Disposable.None; private mouseDownInfo: { lineNumber: number } | null = null; private enabled = false; @@ -588,10 +588,6 @@ export class DirtyDiffController extends Disposable implements IEditorContributi } } - getId(): string { - return DirtyDiffController.ID; - } - canNavigate(): boolean { return this.currentIndex === -1 || (!!this.model && this.model.changes.length > 1); } @@ -676,7 +672,10 @@ export class DirtyDiffController extends Disposable implements IEditorContributi const disposables = new DisposableStore(); disposables.add(Event.once(this.widget.onDidClose)(this.close, this)); - disposables.add(model.onDidChange(this.onDidModelChange, this)); + Event.chain(model.onDidChange) + .filter(e => e.diff.length > 0) + .map(e => e.diff) + .event(this.onDidModelChange, this, disposables); disposables.add(this.widget); disposables.add(toDisposable(() => { @@ -951,9 +950,26 @@ function compareChanges(a: IChange, b: IChange): number { return a.originalEndLineNumber - b.originalEndLineNumber; } +function createProviderComparer(uri: URI): (a: ISCMProvider, b: ISCMProvider) => number { + return (a, b) => { + const aIsParent = isEqualOrParent(uri, a.rootUri!); + const bIsParent = isEqualOrParent(uri, b.rootUri!); + + if (aIsParent && bIsParent) { + return a.rootUri!.fsPath.length - b.rootUri!.fsPath.length; + } else if (aIsParent) { + return -1; + } else if (bIsParent) { + return 1; + } else { + return 0; + } + }; +} + export class DirtyDiffModel extends Disposable { - private _originalModel: ITextModel | null; + private _originalModel: ITextModel | null = null; get original(): ITextModel | null { return this._originalModel; } get modified(): ITextModel | null { return this._editorModel; } @@ -962,13 +978,11 @@ export class DirtyDiffModel extends Disposable { private repositoryDisposables = new Set(); private readonly originalModelDisposables = this._register(new DisposableStore()); - private readonly _onDidChange = new Emitter[]>(); - readonly onDidChange: Event[]> = this._onDidChange.event; + private readonly _onDidChange = new Emitter<{ changes: IChange[], diff: ISplice[] }>(); + readonly onDidChange: Event<{ changes: IChange[], diff: ISplice[] }> = this._onDidChange.event; private _changes: IChange[] = []; - get changes(): IChange[] { - return this._changes; - } + get changes(): IChange[] { return this._changes; } private _editorModel: ITextModel | null; @@ -1022,10 +1036,7 @@ export class DirtyDiffModel extends Disposable { const diff = sortedDiff(this._changes, changes, compareChanges); this._changes = changes; - - if (diff.length > 0) { - this._onDidChange.fire(diff); - } + this._onDidChange.fire({ changes, diff }); }); } @@ -1082,13 +1093,25 @@ export class DirtyDiffModel extends Disposable { }); } - private getOriginalResource(): Promise { + private async getOriginalResource(): Promise { if (!this._editorModel) { return Promise.resolve(null); } const uri = this._editorModel.uri; - return first(this.scmService.repositories.map(r => () => r.provider.getOriginalResource(uri))); + const providers = this.scmService.repositories.map(r => r.provider); + const rootedProviders = providers.filter(p => !!p.rootUri); + + rootedProviders.sort(createProviderComparer(uri)); + + const result = await first(rootedProviders.map(p => () => p.getOriginalResource(uri))); + + if (result) { + return result; + } + + const nonRootedProviders = providers.filter(p => !p.rootUri); + return first(nonRootedProviders.map(p => () => p.getOriginalResource(uri))); } findNextClosestChange(lineNumber: number, inclusive = true): number { @@ -1177,6 +1200,10 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor const onDidChangeDiffWidthConfiguration = Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.diffDecorationsGutterWidth')); onDidChangeDiffWidthConfiguration(this.onDidChangeDiffWidthConfiguration, this); this.onDidChangeDiffWidthConfiguration(); + + const onDidChangeDiffVisibilityConfiguration = Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.diffDecorationsGutterVisibility')); + onDidChangeDiffVisibilityConfiguration(this.onDidChangeDiffVisibiltiyConfiguration, this); + this.onDidChangeDiffVisibiltiyConfiguration(); } private onDidChangeConfiguration(): void { @@ -1199,6 +1226,16 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor this.stylesheet.innerHTML = `.monaco-editor .dirty-diff-modified,.monaco-editor .dirty-diff-added{border-left-width:${width}px;}`; } + private onDidChangeDiffVisibiltiyConfiguration(): void { + const visibility = this.configurationService.getValue('scm.diffDecorationsGutterVisibility'); + + this.stylesheet.innerHTML = ` + .monaco-editor .dirty-diff-modified, .monaco-editor .dirty-diff-added, .monaco-editor .dirty-diff-deleted { + opacity: ${visibility === 'always' ? 1 : 0}; + } + `; + } + private enable(): void { if (this.enabled) { this.disable(); @@ -1278,7 +1315,7 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor } } -registerEditorContribution(DirtyDiffController); +registerEditorContribution(DirtyDiffController.ID, DirtyDiffController); registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const editorGutterModifiedBackgroundColor = theme.getColor(editorGutterModifiedBackground); @@ -1286,10 +1323,14 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { collector.addRule(` .monaco-editor .dirty-diff-modified { border-left: 3px solid ${editorGutterModifiedBackgroundColor}; + transition: opacity 0.5s; } .monaco-editor .dirty-diff-modified:before { background: ${editorGutterModifiedBackgroundColor}; } + .monaco-editor .margin:hover .dirty-diff-modified { + opacity: 1; + } `); } @@ -1298,10 +1339,14 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { collector.addRule(` .monaco-editor .dirty-diff-added { border-left: 3px solid ${editorGutterAddedBackgroundColor}; + transition: opacity 0.5s; } .monaco-editor .dirty-diff-added:before { background: ${editorGutterAddedBackgroundColor}; } + .monaco-editor .margin:hover .dirty-diff-added { + opacity: 1; + } `); } @@ -1310,10 +1355,14 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { collector.addRule(` .monaco-editor .dirty-diff-deleted:after { border-left: 4px solid ${editorGutteDeletedBackgroundColor}; + transition: opacity 0.5s; } .monaco-editor .dirty-diff-deleted:before { background: ${editorGutteDeletedBackgroundColor}; } + .monaco-editor .margin:hover .dirty-diff-added { + opacity: 1; + } `); } }); diff --git a/src/vs/workbench/contrib/scm/browser/mainPanel.ts b/src/vs/workbench/contrib/scm/browser/mainPanel.ts index 2dbb63f3b0..4a3f174417 100644 --- a/src/vs/workbench/contrib/scm/browser/mainPanel.ts +++ b/src/vs/workbench/contrib/scm/browser/mainPanel.ts @@ -10,7 +10,6 @@ import { basename } from 'vs/base/common/resources'; import { IDisposable, dispose, Disposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; import { append, $, toggleClass } from 'vs/base/browser/dom'; -import { List } from 'vs/base/browser/ui/list/listWidget'; import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListEvent } from 'vs/base/browser/ui/list/list'; import { ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; @@ -26,7 +25,7 @@ import { ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionba import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { Command } from 'vs/editor/common/modes'; -import { renderOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; +import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewDescriptor } from 'vs/workbench/common/views'; @@ -82,8 +81,8 @@ class StatusBarActionViewItem extends ActionViewItem { } updateLabel(): void { - if (this.options.label) { - this.label.innerHTML = renderOcticons(this.getAction().label); + if (this.options.label && this.label) { + this.label.innerHTML = renderCodicons(this.getAction().label); } } } @@ -173,7 +172,7 @@ export class MainPanel extends ViewletPanel { static readonly ID = 'scm.mainPanel'; static readonly TITLE = localize('scm providers', "Source Control Providers"); - private list: List; + private list!: WorkbenchList; constructor( protected viewModel: IViewModel, diff --git a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css index 563eefe857..caa4fd6cf6 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css +++ b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css @@ -41,8 +41,25 @@ } .scm-viewlet .monaco-list-row > .scm-provider > .monaco-action-bar .action-item { + padding: 0 4px; overflow: hidden; text-overflow: ellipsis; + display: flex; + align-items: center; +} + +.scm-viewlet .monaco-list-row > .scm-provider > .monaco-action-bar .action-label .codicon { + font-size: 14px; +} + +.scm-viewlet .monaco-list-row > .scm-provider > .monaco-action-bar .action-item:last-of-type { + padding-right: 0; +} + +.scm-viewlet .scm-provider > .name, +.scm-viewlet .scm-provider > .count { + display: flex; + align-items: center; } .scm-viewlet .scm-provider > .count { @@ -97,7 +114,7 @@ } .scm-viewlet .monaco-list-row .resource-group > .count { - padding: 0 8px; + padding: 0 12px 0 8px; display: flex; } @@ -106,6 +123,7 @@ height: 100%; background-repeat: no-repeat; background-position: 50% 50%; + margin-right: 8px; } .scm-viewlet .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions { @@ -142,7 +160,7 @@ .scm-viewlet .scm-editor { box-sizing: border-box; - padding: 5px 9px 5px 16px; + padding: 5px 12px 5px 16px; } .scm-viewlet .scm-editor.hidden { @@ -170,3 +188,7 @@ width: 8px !important; margin-right: 0 !important; } + +.scm-viewlet .scm-status.show-file-icons.hide-arrows.tree-view-mode .monaco-tl-indent .indent-guide:first-child { + border: none; +} diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPanel.ts b/src/vs/workbench/contrib/scm/browser/repositoryPanel.ts index 3a56f002f3..7aa60aff88 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPanel.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPanel.ts @@ -32,12 +32,12 @@ import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { format } from 'vs/base/common/strings'; import { WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ThrottledDelayer } from 'vs/base/common/async'; +import { ThrottledDelayer, disposableTimeout } from 'vs/base/common/async'; import { INotificationService } from 'vs/platform/notification/common/notification'; import * as platform from 'vs/base/common/platform'; import { ITreeNode, ITreeFilter, ITreeSorter, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; +import { ResourceTree, IResourceNode } from 'vs/base/common/resourceTree'; import { ISequence, ISplice } from 'vs/base/common/sequence'; -import { ResourceTree, IBranchNode, INode } from 'vs/base/common/resourceTree'; import { ObjectTree, ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/tree/objectTree'; import { Iterator } from 'vs/base/common/iterator'; import { ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; @@ -50,8 +50,10 @@ import { localize } from 'vs/nls'; import { flatten } from 'vs/base/common/arrays'; import { memoize } from 'vs/base/common/decorators'; import { IWorkbenchThemeService, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; -type TreeElement = ISCMResourceGroup | IBranchNode | ISCMResource; +type TreeElement = ISCMResourceGroup | IResourceNode | ISCMResource; interface ResourceGroupTemplate { readonly name: HTMLElement; @@ -63,7 +65,7 @@ interface ResourceGroupTemplate { class ResourceGroupRenderer implements ICompressibleTreeRenderer { - static TEMPLATE_ID = 'resource group'; + static readonly TEMPLATE_ID = 'resource group'; get templateId(): string { return ResourceGroupRenderer.TEMPLATE_ID; } constructor( @@ -130,11 +132,11 @@ interface ResourceTemplate { class MultipleSelectionActionRunner extends ActionRunner { - constructor(private getSelectedResources: () => (ISCMResource | IBranchNode)[]) { + constructor(private getSelectedResources: () => (ISCMResource | IResourceNode)[]) { super(); } - runAction(action: IAction, context: ISCMResource | IBranchNode): Promise { + runAction(action: IAction, context: ISCMResource | IResourceNode): Promise { if (!(action instanceof MenuItemAction)) { return super.runAction(action, context); } @@ -142,21 +144,21 @@ class MultipleSelectionActionRunner extends ActionRunner { const selection = this.getSelectedResources(); const contextIsSelected = selection.some(s => s === context); const actualContext = contextIsSelected ? selection : [context]; - const args = flatten(actualContext.map(e => ResourceTree.isBranchNode(e) ? ResourceTree.collect(e) : [e])); + const args = flatten(actualContext.map(e => ResourceTree.isResourceNode(e) ? ResourceTree.collect(e) : [e])); return action.run(...args); } } -class ResourceRenderer implements ICompressibleTreeRenderer, FuzzyScore, ResourceTemplate> { +class ResourceRenderer implements ICompressibleTreeRenderer, FuzzyScore, ResourceTemplate> { - static TEMPLATE_ID = 'resource'; + static readonly TEMPLATE_ID = 'resource'; get templateId(): string { return ResourceRenderer.TEMPLATE_ID; } constructor( private viewModelProvider: () => ViewModel, private labels: ResourceLabels, private actionViewItemProvider: IActionViewItemProvider, - private getSelectedResources: () => (ISCMResource | IBranchNode)[], + private getSelectedResources: () => (ISCMResource | IResourceNode)[], private themeService: IThemeService, private menus: SCMMenus ) { } @@ -177,16 +179,17 @@ class ResourceRenderer implements ICompressibleTreeRenderer | ITreeNode, FuzzyScore>, index: number, template: ResourceTemplate): void { + renderElement(node: ITreeNode | ITreeNode, FuzzyScore>, index: number, template: ResourceTemplate): void { template.elementDisposables.dispose(); const elementDisposables = new DisposableStore(); const resourceOrFolder = node.element; const theme = this.themeService.getTheme(); - const icon = !ResourceTree.isBranchNode(resourceOrFolder) && (theme.type === LIGHT ? resourceOrFolder.decorations.icon : resourceOrFolder.decorations.iconDark); + const iconResource = ResourceTree.isResourceNode(resourceOrFolder) ? resourceOrFolder.element : resourceOrFolder; + const icon = iconResource && (theme.type === LIGHT ? iconResource.decorations.icon : iconResource.decorations.iconDark); - const uri = ResourceTree.isBranchNode(resourceOrFolder) ? resourceOrFolder.uri : resourceOrFolder.sourceUri; - const fileKind = ResourceTree.isBranchNode(resourceOrFolder) ? FileKind.FOLDER : FileKind.FILE; + const uri = ResourceTree.isResourceNode(resourceOrFolder) ? resourceOrFolder.uri : resourceOrFolder.sourceUri; + const fileKind = ResourceTree.isResourceNode(resourceOrFolder) ? FileKind.FOLDER : FileKind.FILE; const viewModel = this.viewModelProvider(); template.fileLabel.setFile(uri, { @@ -199,17 +202,23 @@ class ResourceRenderer implements ICompressibleTreeRenderer | ITreeNode, FuzzyScore>, index: number, template: ResourceTemplate): void { + disposeElement(resource: ITreeNode | ITreeNode, FuzzyScore>, index: number, template: ResourceTemplate): void { template.elementDisposables.dispose(); } - renderCompressedElements(node: ITreeNode | ICompressedTreeNode>, FuzzyScore>, index: number, template: ResourceTemplate, height: number | undefined): void { + renderCompressedElements(node: ITreeNode | ICompressedTreeNode>, FuzzyScore>, index: number, template: ResourceTemplate, height: number | undefined): void { template.elementDisposables.dispose(); const elementDisposables = new DisposableStore(); - const compressed = node.element as ICompressedTreeNode>; + const compressed = node.element as ICompressedTreeNode>; const folder = compressed.elements[compressed.elements.length - 1]; const label = compressed.elements.map(e => e.name).join('/'); @@ -259,7 +268,7 @@ class ResourceRenderer implements ICompressibleTreeRenderer | ICompressedTreeNode>, FuzzyScore>, index: number, template: ResourceTemplate, height: number | undefined): void { + disposeCompressedElements(node: ITreeNode | ICompressedTreeNode>, FuzzyScore>, index: number, template: ResourceTemplate, height: number | undefined): void { template.elementDisposables.dispose(); } @@ -274,7 +283,7 @@ class ProviderListDelegate implements IListVirtualDelegate { getHeight() { return 22; } getTemplateId(element: TreeElement) { - if (ResourceTree.isBranchNode(element) || isSCMResource(element)) { + if (ResourceTree.isResourceNode(element) || isSCMResource(element)) { return ResourceRenderer.TEMPLATE_ID; } else { return ResourceGroupRenderer.TEMPLATE_ID; @@ -285,7 +294,7 @@ class ProviderListDelegate implements IListVirtualDelegate { class SCMTreeFilter implements ITreeFilter { filter(element: TreeElement): boolean { - if (ResourceTree.isBranchNode(element)) { + if (ResourceTree.isResourceNode(element)) { return true; } else if (isSCMResourceGroup(element)) { return element.elements.length > 0 || !element.hideWhenEmpty; @@ -311,15 +320,15 @@ export class SCMTreeSorter implements ITreeSorter { return 0; } - const oneIsDirectory = ResourceTree.isBranchNode(one); - const otherIsDirectory = ResourceTree.isBranchNode(other); + const oneIsDirectory = ResourceTree.isResourceNode(one); + const otherIsDirectory = ResourceTree.isResourceNode(other); if (oneIsDirectory !== otherIsDirectory) { return oneIsDirectory ? -1 : 1; } - const oneName = ResourceTree.isBranchNode(one) ? one.name : basename((one as ISCMResource).sourceUri); - const otherName = ResourceTree.isBranchNode(other) ? other.name : basename((other as ISCMResource).sourceUri); + const oneName = ResourceTree.isResourceNode(one) ? one.name : basename((one as ISCMResource).sourceUri); + const otherName = ResourceTree.isResourceNode(other) ? other.name : basename((other as ISCMResource).sourceUri); return compareFileNames(oneName, otherName); } @@ -328,7 +337,7 @@ export class SCMTreeSorter implements ITreeSorter { export class SCMTreeKeyboardNavigationLabelProvider implements ICompressibleKeyboardNavigationLabelProvider { getKeyboardNavigationLabel(element: TreeElement): { toString(): string; } | undefined { - if (ResourceTree.isBranchNode(element)) { + if (ResourceTree.isResourceNode(element)) { return element.name; } else if (isSCMResourceGroup(element)) { return element.label; @@ -338,7 +347,7 @@ export class SCMTreeKeyboardNavigationLabelProvider implements ICompressibleKeyb } getCompressedNodeKeyboardNavigationLabel(elements: TreeElement[]): { toString(): string | undefined; } | undefined { - const folders = elements as IBranchNode[]; + const folders = elements as IResourceNode[]; return folders.map(e => e.name).join('/'); } } @@ -346,7 +355,7 @@ export class SCMTreeKeyboardNavigationLabelProvider implements ICompressibleKeyb class SCMResourceIdentityProvider implements IIdentityProvider { getId(element: TreeElement): string { - if (ResourceTree.isBranchNode(element)) { + if (ResourceTree.isResourceNode(element)) { const group = element.context; return `${group.provider.contextValue}/${group.id}/$FOLDER/${element.uri.toString()}`; } else if (isSCMResource(element)) { @@ -375,22 +384,17 @@ function groupItemAsTreeElement(item: IGroupItem, mode: ViewModelMode): ICompres return { element: item.group, children, incompressible: true, collapsible: true }; } -function asTreeElement(node: INode, incompressible: boolean): ICompressedTreeElement { - if (ResourceTree.isBranchNode(node)) { - return { - element: node, - children: Iterator.map(node.children, node => asTreeElement(node, false)), - incompressible, - collapsed: false - }; - } - - return { element: node.element, incompressible: true }; +function asTreeElement(node: IResourceNode, forceIncompressible: boolean): ICompressedTreeElement { + return { + element: (node.childrenCount === 0 && node.element) ? node.element : node, + children: Iterator.map(node.children, node => asTreeElement(node, false)), + incompressible: !!node.element || forceIncompressible + }; } const enum ViewModelMode { - List, - Tree + List = 'list', + Tree = 'tree' } class ViewModel { @@ -401,6 +405,17 @@ class ViewModel { get mode(): ViewModelMode { return this._mode; } set mode(mode: ViewModelMode) { this._mode = mode; + + for (const item of this.items) { + item.tree.clear(); + + if (mode === ViewModelMode.Tree) { + for (const resource of item.resources) { + item.tree.add(resource.sourceUri, resource); + } + } + } + this.refresh(); this._onDidChangeMode.fire(mode); } @@ -408,12 +423,15 @@ class ViewModel { private items: IGroupItem[] = []; private visibilityDisposables = new DisposableStore(); private scrollTop: number | undefined; + private firstVisible = true; private disposables = new DisposableStore(); constructor( private groups: ISequence, private tree: ObjectTree, - private _mode: ViewModelMode + private _mode: ViewModelMode, + @IEditorService protected editorService: IEditorService, + @IConfigurationService protected configurationService: IConfigurationService, ) { } private onDidSpliceGroups({ start, deleteCount, toInsert }: ISplice): void { @@ -427,10 +445,12 @@ class ViewModel { group.onDidSplice(splice => this.onDidSpliceGroup(item, splice)) ); - const item = { group, resources, tree, disposable }; + const item: IGroupItem = { group, resources, tree, disposable }; - for (const resource of resources) { - item.tree.add(resource.sourceUri, resource); + if (this._mode === ViewModelMode.Tree) { + for (const resource of resources) { + item.tree.add(resource.sourceUri, resource); + } } itemsToInsert.push(item); @@ -446,14 +466,18 @@ class ViewModel { } private onDidSpliceGroup(item: IGroupItem, { start, deleteCount, toInsert }: ISplice): void { - for (const resource of toInsert) { - item.tree.add(resource.sourceUri, resource); + if (this._mode === ViewModelMode.Tree) { + for (const resource of toInsert) { + item.tree.add(resource.sourceUri, resource); + } } const deleted = item.resources.splice(start, deleteCount, ...toInsert); - for (const resource of deleted) { - item.tree.delete(resource.sourceUri); + if (this._mode === ViewModelMode.Tree) { + for (const resource of deleted) { + item.tree.delete(resource.sourceUri); + } } this.refresh(item); @@ -469,6 +493,9 @@ class ViewModel { this.tree.scrollTop = this.scrollTop; this.scrollTop = undefined; } + + this.editorService.onDidActiveEditorChange(this.onDidActiveEditorChange, this, this.visibilityDisposables); + this.onDidActiveEditorChange(); } else { this.visibilityDisposables.dispose(); this.onDidSpliceGroups({ start: 0, deleteCount: this.items.length, toInsert: [] }); @@ -484,6 +511,42 @@ class ViewModel { } } + private onDidActiveEditorChange(): void { + if (!this.configurationService.getValue('scm.autoReveal')) { + return; + } + + if (this.firstVisible) { + this.firstVisible = false; + this.visibilityDisposables.add(disposableTimeout(() => this.onDidActiveEditorChange(), 250)); + return; + } + + const editor = this.editorService.activeEditor; + + if (!editor) { + return; + } + + const uri = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); + + if (!uri) { + return; + } + + // go backwards from last group + for (let i = this.items.length - 1; i >= 0; i--) { + const node = this.items[i].tree.getNode(uri); + + if (node && node.element) { + this.tree.reveal(node.element); + this.tree.setSelection([node.element]); + this.tree.setFocus([node.element]); + return; + } + } + } + dispose(): void { this.visibilityDisposables.dispose(); this.disposables.dispose(); @@ -524,15 +587,16 @@ export class RepositoryPanel extends ViewletPanel { private cachedHeight: number | undefined = undefined; private cachedWidth: number | undefined = undefined; - private inputBoxContainer: HTMLElement; - private inputBox: InputBox; - private listContainer: HTMLElement; - private tree: ObjectTree; - private viewModel: ViewModel; - private listLabels: ResourceLabels; + private inputBoxContainer!: HTMLElement; + private inputBox!: InputBox; + private listContainer!: HTMLElement; + private tree!: ObjectTree; + private viewModel!: ViewModel; + private listLabels!: ResourceLabels; private menus: SCMMenus; private toggleViewModelModeAction: ToggleViewModeAction | undefined; protected contextKeyService: IContextKeyService; + private commitTemplate = ''; constructor( readonly repository: ISCMRepository, @@ -547,7 +611,8 @@ export class RepositoryPanel extends ViewletPanel { @IInstantiationService protected instantiationService: IInstantiationService, @IConfigurationService protected configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, - @IMenuService protected menuService: IMenuService + @IMenuService protected menuService: IMenuService, + @IStorageService private storageService: IStorageService ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService); @@ -634,10 +699,10 @@ export class RepositoryPanel extends ViewletPanel { this._register(this.inputBox.onDidHeightChange(() => this.layoutBody())); if (this.repository.provider.onDidChangeCommitTemplate) { - this._register(this.repository.provider.onDidChangeCommitTemplate(this.updateInputBox, this)); + this._register(this.repository.provider.onDidChangeCommitTemplate(this.onDidChangeCommitTemplate, this)); } - this.updateInputBox(); + this.onDidChangeCommitTemplate(); // Input box visibility this._register(this.repository.input.onDidChangeVisibility(this.updateInputBoxVisibility, this)); @@ -683,33 +748,38 @@ export class RepositoryPanel extends ViewletPanel { this._register(Event.chain(this.tree.onDidOpen) .map(e => e.elements[0]) - .filter(e => !!e && !ResourceTree.isBranchNode(e) && isSCMResource(e)) + .filter(e => !!e && !isSCMResourceGroup(e) && !ResourceTree.isResourceNode(e)) .on(this.open, this)); this._register(Event.chain(this.tree.onDidPin) .map(e => e.elements[0]) - .filter(e => !!e && !ResourceTree.isBranchNode(e) && isSCMResource(e)) + .filter(e => !!e && !isSCMResourceGroup(e) && !ResourceTree.isResourceNode(e)) .on(this.pin, this)); this._register(this.tree.onContextMenu(this.onListContextMenu, this)); this._register(this.tree); - const mode = this.configurationService.getValue<'tree' | 'list'>('scm.defaultViewMode') === 'list' ? ViewModelMode.List : ViewModelMode.Tree; - this.viewModel = new ViewModel(this.repository.provider.groups, this.tree, mode); + let mode = this.configurationService.getValue<'tree' | 'list'>('scm.defaultViewMode') === 'list' ? ViewModelMode.List : ViewModelMode.Tree; + + const rootUri = this.repository.provider.rootUri; + + if (typeof rootUri !== 'undefined') { + const storageMode = this.storageService.get(`scm.repository.viewMode:${rootUri.toString()}`, StorageScope.WORKSPACE) as ViewModelMode; + + if (typeof storageMode === 'string') { + mode = storageMode; + } + } + + this.viewModel = this.instantiationService.createInstance(ViewModel, this.repository.provider.groups, this.tree, mode); this._register(this.viewModel); addClass(this.listContainer, 'file-icon-themable-tree'); addClass(this.listContainer, 'show-file-icons'); - const updateIndentStyles = (theme: IFileIconTheme) => { - toggleClass(this.listContainer, 'list-view-mode', this.viewModel.mode === ViewModelMode.List); - toggleClass(this.listContainer, 'align-icons-and-twisties', this.viewModel.mode === ViewModelMode.Tree && theme.hasFileIcons && !theme.hasFolderIcons); - toggleClass(this.listContainer, 'hide-arrows', this.viewModel.mode === ViewModelMode.Tree && theme.hidesExplorerArrows === true); - }; - - updateIndentStyles(this.themeService.getFileIconTheme()); - this._register(this.themeService.onDidFileIconThemeChange(updateIndentStyles)); - this._register(this.viewModel.onDidChangeMode(() => updateIndentStyles(this.themeService.getFileIconTheme()))); + this.updateIndentStyles(this.themeService.getFileIconTheme()); + this._register(this.themeService.onDidFileIconThemeChange(this.updateIndentStyles, this)); + this._register(this.viewModel.onDidChangeMode(this.onDidChangeMode, this)); this.toggleViewModelModeAction = new ToggleViewModeAction(this.viewModel); this._register(this.toggleViewModelModeAction); @@ -719,6 +789,25 @@ export class RepositoryPanel extends ViewletPanel { this.updateActions(); } + private updateIndentStyles(theme: IFileIconTheme): void { + toggleClass(this.listContainer, 'list-view-mode', this.viewModel.mode === ViewModelMode.List); + toggleClass(this.listContainer, 'tree-view-mode', this.viewModel.mode === ViewModelMode.Tree); + toggleClass(this.listContainer, 'align-icons-and-twisties', this.viewModel.mode === ViewModelMode.Tree && theme.hasFileIcons && !theme.hasFolderIcons); + toggleClass(this.listContainer, 'hide-arrows', this.viewModel.mode === ViewModelMode.Tree && theme.hidesExplorerArrows === true); + } + + private onDidChangeMode(): void { + this.updateIndentStyles(this.themeService.getFileIconTheme()); + + const rootUri = this.repository.provider.rootUri; + + if (typeof rootUri === 'undefined') { + return; + } + + this.storageService.store(`scm.repository.viewMode:${rootUri.toString()}`, this.viewModel.mode, StorageScope.WORKSPACE); + } + layoutBody(height: number | undefined = this.cachedHeight, width: number | undefined = this.cachedWidth): void { if (height === undefined) { return; @@ -809,12 +898,16 @@ export class RepositoryPanel extends ViewletPanel { const element = e.element; let actions: IAction[] = []; - if (ResourceTree.isBranchNode(element)) { - actions = this.menus.getResourceFolderContextActions(element.context); - } else if (isSCMResource(element)) { - actions = this.menus.getResourceContextActions(element); - } else { + if (isSCMResourceGroup(element)) { actions = this.menus.getResourceGroupContextActions(element); + } else if (ResourceTree.isResourceNode(element)) { + if (element.element) { + actions = this.menus.getResourceContextActions(element.element); + } else { + actions = this.menus.getResourceFolderContextActions(element.context); + } + } else { + actions = this.menus.getResourceContextActions(element); } this.contextMenuService.showContextMenu({ @@ -825,17 +918,24 @@ export class RepositoryPanel extends ViewletPanel { }); } - private getSelectedResources(): (ISCMResource | IBranchNode)[] { + private getSelectedResources(): (ISCMResource | IResourceNode)[] { return this.tree.getSelection() .filter(r => !!r && !isSCMResourceGroup(r))! as any; } - private updateInputBox(): void { - if (typeof this.repository.provider.commitTemplate === 'undefined' || !this.repository.input.visible || this.inputBox.value) { + private onDidChangeCommitTemplate(): void { + if (typeof this.repository.provider.commitTemplate === 'undefined' || !this.repository.input.visible) { return; } - this.inputBox.value = this.repository.provider.commitTemplate; + const oldCommitTemplate = this.commitTemplate; + this.commitTemplate = this.repository.provider.commitTemplate; + + if (this.inputBox.value && this.inputBox.value !== oldCommitTemplate) { + return; + } + + this.inputBox.value = this.commitTemplate; } private updateInputBoxVisibility(): void { diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 6ec66eebce..bce8fae3ff 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -28,7 +28,7 @@ import { SCMService } from 'vs/workbench/contrib/scm/common/scmService'; class OpenSCMViewletAction extends ShowViewletAction { static readonly ID = VIEWLET_ID; - static LABEL = localize('toggleGitViewlet', "Show Git"); + static readonly LABEL = localize('toggleGitViewlet', "Show Git"); constructor(id: string, label: string, @IViewletService viewletService: IViewletService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService) { super(id, label, VIEWLET_ID, viewletService, editorGroupService, layoutService); @@ -91,6 +91,16 @@ Registry.as(ConfigurationExtensions.Configuration).regis default: 3, description: localize('diffGutterWidth', "Controls the width(px) of diff decorations in gutter (added & modified).") }, + 'scm.diffDecorationsGutterVisibility': { + type: 'string', + enum: ['always', 'hover'], + enumDescriptions: [ + localize('scm.diffDecorationsGutterVisibility.always', "Show the diff decorator in the gutter at all times."), + localize('scm.diffDecorationsGutterVisibility.hover', "Show the diff decorator in the gutter only on hover.") + ], + description: localize('scm.diffDecorationsGutterVisibility', "Controls the visibilty of the Source Control diff decorator in the gutter."), + default: 'always' + }, 'scm.alwaysShowActions': { type: 'boolean', description: localize('alwaysShowActions', "Controls whether inline actions are always visible in the Source Control view."), @@ -115,8 +125,13 @@ Registry.as(ConfigurationExtensions.Configuration).regis localize('scm.defaultViewMode.list', "Show the repository changes as a list.") ], description: localize('scm.defaultViewMode', "Controls the default Source Control repository view mode."), - default: 'tree' - } + default: 'list' + }, + 'scm.autoReveal': { + type: 'boolean', + description: localize('autoReveal', "Controls whether the SCM view should automatically reveal and select files when opening them."), + default: true + }, } }); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index 8253c479fc..781c3ca350 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -54,7 +54,7 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel { private static readonly STATE_KEY = 'workbench.scm.views.state'; - private el: HTMLElement; + private el!: HTMLElement; private message: HTMLElement; private menus: SCMMenus; private _repositories: ISCMRepository[] = []; diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index 51675a614b..e3e528f9f3 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -56,8 +56,8 @@ export interface ISCMProvider extends IDisposable { readonly rootUri?: URI; readonly count?: number; - readonly commitTemplate?: string; - readonly onDidChangeCommitTemplate?: Event; + readonly commitTemplate: string; + readonly onDidChangeCommitTemplate: Event; readonly onDidChangeStatusBarCommands?: Event; readonly acceptInputCommand?: Command; readonly statusBarCommands?: Command[]; diff --git a/src/vs/workbench/contrib/search/browser/media/searchview.css b/src/vs/workbench/contrib/search/browser/media/searchview.css index eb1c5c4f05..bdd4ba27c6 100644 --- a/src/vs/workbench/contrib/search/browser/media/searchview.css +++ b/src/vs/workbench/contrib/search/browser/media/searchview.css @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ .search-view .search-widgets-container { - margin: 0px 9px 0 2px; + margin: 0px 12px 0 2px; padding-top: 6px; } @@ -25,7 +25,7 @@ .search-view .search-widget .search-container, .search-view .search-widget .replace-container { - margin-left: 17px; + margin-left: 18px; } .search-view .search-widget .monaco-inputbox > .wrapper { @@ -82,7 +82,7 @@ } .search-view .search-widget .replace-container .monaco-action-bar { - margin-left: 3px; + margin-left: 0; } .search-view .search-widget .replace-container .monaco-action-bar { @@ -91,8 +91,12 @@ .search-view .search-widget .replace-container .monaco-action-bar .action-item .codicon { background-repeat: no-repeat; - width: 20px; + width: 25px; height: 25px; + margin-right: 0; + display: flex; + align-items: center; + justify-content: center; } .search-view .query-details { @@ -103,10 +107,9 @@ .search-view .query-details .more { position: absolute; - margin-right: 0.3em; - right: 0; + right: -2px; cursor: pointer; - width: 16px; + width: 25px; height: 16px; z-index: 2; /* Force it above the search results message, which has a negative top margin */ } diff --git a/src/vs/workbench/contrib/search/browser/openAnythingHandler.ts b/src/vs/workbench/contrib/search/browser/openAnythingHandler.ts index d7f244ea48..2a80b13eac 100644 --- a/src/vs/workbench/contrib/search/browser/openAnythingHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openAnythingHandler.ts @@ -68,7 +68,7 @@ export class OpenAnythingHandler extends QuickOpenHandler { } private updateHandlers(configuration: IWorkbenchSearchConfiguration): void { - this.includeSymbols = configuration && configuration.search && configuration.search.quickOpen && configuration.search.quickOpen.includeSymbols; + this.includeSymbols = configuration?.search?.quickOpen?.includeSymbols; // Files this.openFileHandler.setOptions({ diff --git a/src/vs/workbench/contrib/search/browser/openFileHandler.ts b/src/vs/workbench/contrib/search/browser/openFileHandler.ts index 8cc9c4378f..5cc388272b 100644 --- a/src/vs/workbench/contrib/search/browser/openFileHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openFileHandler.ts @@ -8,10 +8,8 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; import { defaultGenerator } from 'vs/base/common/idGenerator'; import { untildify } from 'vs/base/common/labels'; -import { Schemas } from 'vs/base/common/network'; import * as objects from 'vs/base/common/objects'; -import { isAbsolute } from 'vs/base/common/path'; -import { basename, dirname } from 'vs/base/common/resources'; +import { basename, dirname, toLocalResource } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { QuickOpenEntry, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; @@ -33,6 +31,8 @@ import { EditorInput, IWorkbenchEditorConfiguration } from 'vs/workbench/common/ import { IFileQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; import { IFileQuery, IFileSearchStats, ISearchComplete, ISearchService } from 'vs/workbench/services/search/common/search'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; @@ -50,7 +50,7 @@ export class FileEntry extends EditorQuickOpenEntry { private resource: URI, private name: string, private description: string, - private icon: string, + private icon: string | undefined, @IEditorService editorService: IEditorService, @IModeService private readonly modeService: IModeService, @IModelService private readonly modelService: IModelService, @@ -78,7 +78,7 @@ export class FileEntry extends EditorQuickOpenEntry { return this.description; } - getIcon(): string { + getIcon(): string | undefined { return this.icon; } @@ -122,8 +122,10 @@ export class OpenFileHandler extends QuickOpenHandler { @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @ISearchService private readonly searchService: ISearchService, @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, @IFileService private readonly fileService: IFileService, - @ILabelService private readonly labelService: ILabelService + @ILabelService private readonly labelService: ILabelService, + @IRemotePathService private readonly remotePathService: IRemotePathService, ) { super(); @@ -186,15 +188,13 @@ export class OpenFileHandler extends QuickOpenHandler { private async getAbsolutePathResult(query: IPreparedQuery): Promise { const detildifiedQuery = untildify(query.original, this.environmentService.userHome); - if (isAbsolute(detildifiedQuery)) { - const workspaceFolders = this.contextService.getWorkspace().folders; - const resource = workspaceFolders[0] && workspaceFolders[0].uri.scheme !== Schemas.file ? - workspaceFolders[0].uri.with({ path: detildifiedQuery }) : - URI.file(detildifiedQuery); + if ((await this.remotePathService.path).isAbsolute(detildifiedQuery)) { + const resource = toLocalResource( + await this.remotePathService.fileURI(detildifiedQuery), + this.workbenchEnvironmentService.configuration.remoteAuthority); try { const stat = await this.fileService.resolve(resource); - return stat.isDirectory ? undefined : resource; } catch (error) { // ignore diff --git a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts index 7decb4fb22..fce5d88875 100644 --- a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts @@ -14,7 +14,7 @@ import * as filters from 'vs/base/common/filters'; import * as strings from 'vs/base/common/strings'; import { Range } from 'vs/editor/common/core/range'; import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; -import { symbolKindToCssClass, SymbolTag } from 'vs/editor/common/modes'; +import { SymbolKinds, SymbolTag } from 'vs/editor/common/modes'; import { IResourceInput } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -47,7 +47,7 @@ class SymbolEntry extends EditorQuickOpenEntry { this.score = score; } - getHighlights(): [IHighlight[] /* Label */, IHighlight[] | undefined /* Description */, IHighlight[] | undefined /* Detail */] { + getHighlights(): [IHighlight[] | undefined /* Label */, IHighlight[] | undefined /* Description */, IHighlight[] | undefined /* Detail */] { return [this.isDeprecated() ? [] : filters.createMatches(this.score), undefined, undefined]; } @@ -73,7 +73,7 @@ class SymbolEntry extends EditorQuickOpenEntry { } getIcon(): string { - return symbolKindToCssClass(this.bearing.kind); + return SymbolKinds.toCssClassName(this.bearing.kind); } getLabelOptions(): IIconLabelValueOptions | undefined { @@ -104,7 +104,7 @@ class SymbolEntry extends EditorQuickOpenEntry { const scheme = this.bearing.location.uri ? this.bearing.location.uri.scheme : undefined; if (scheme === Schemas.http || scheme === Schemas.https) { if (mode === Mode.OPEN || mode === Mode.OPEN_IN_BACKGROUND) { - this.openerService.open(this.bearing.location.uri); // support http/https resources (https://github.com/Microsoft/vscode/issues/58924)) + this.openerService.open(this.bearing.location.uri, { fromUserGesture: true }); // support http/https resources (https://github.com/Microsoft/vscode/issues/58924)) } } else { super.run(mode, context); @@ -145,8 +145,8 @@ class SymbolEntry extends EditorQuickOpenEntry { if (res !== 0) { return res; } - let aKind = symbolKindToCssClass(a.bearing.kind); - let bKind = symbolKindToCssClass(b.bearing.kind); + let aKind = SymbolKinds.toCssClassName(a.bearing.kind); + let bKind = SymbolKinds.toCssClassName(b.bearing.kind); return aKind.localeCompare(bKind); } } diff --git a/src/vs/workbench/contrib/search/browser/replaceService.ts b/src/vs/workbench/contrib/search/browser/replaceService.ts index 444d8195c4..45638879e2 100644 --- a/src/vs/workbench/contrib/search/browser/replaceService.ts +++ b/src/vs/workbench/contrib/search/browser/replaceService.ts @@ -130,7 +130,7 @@ export class ReplaceService implements IReplaceService { this.updateReplacePreview(fileMatch).then(() => { if (editor) { const editorControl = editor.getControl(); - if (element instanceof Match) { + if (element instanceof Match && editorControl) { editorControl.revealLineInCenter(element.range().startLineNumber, ScrollType.Immediate); } } diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 2cd398de32..95afb5683d 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -50,7 +50,7 @@ import { registerContributions as searchWidgetContributions } from 'vs/workbench import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { getWorkspaceSymbols } from 'vs/workbench/contrib/search/common/search'; import { ISearchHistoryService, SearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; -import { FileMatchOrMatch, ISearchWorkbenchService, RenderableMatch, SearchWorkbenchService, FileMatch } from 'vs/workbench/contrib/search/common/searchModel'; +import { FileMatchOrMatch, ISearchWorkbenchService, RenderableMatch, SearchWorkbenchService, FileMatch, Match, FolderMatch } from 'vs/workbench/contrib/search/common/searchModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { ISearchConfiguration, ISearchConfigurationProperties, PANEL_ID, VIEWLET_ID, VIEW_CONTAINER, VIEW_ID } from 'vs/workbench/services/search/common/search'; @@ -134,7 +134,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); if (searchView) { const tree: WorkbenchObjectTree = searchView.getControl(); - accessor.get(IInstantiationService).createInstance(RemoveAction, tree, tree.getFocus()[0]).run(); + accessor.get(IInstantiationService).createInstance(RemoveAction, tree, tree.getFocus()[0]!).run(); } } }); @@ -148,7 +148,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); if (searchView) { const tree: WorkbenchObjectTree = searchView.getControl(); - accessor.get(IInstantiationService).createInstance(ReplaceAction, tree, tree.getFocus()[0], searchView).run(); + accessor.get(IInstantiationService).createInstance(ReplaceAction, tree, tree.getFocus()[0] as Match, searchView).run(); } } }); @@ -163,7 +163,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); if (searchView) { const tree: WorkbenchObjectTree = searchView.getControl(); - accessor.get(IInstantiationService).createInstance(ReplaceAllAction, searchView, tree.getFocus()[0]).run(); + accessor.get(IInstantiationService).createInstance(ReplaceAllAction, searchView, tree.getFocus()[0] as FileMatch).run(); } } }); @@ -178,7 +178,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); if (searchView) { const tree: WorkbenchObjectTree = searchView.getControl(); - accessor.get(IInstantiationService).createInstance(ReplaceAllInFolderAction, tree, tree.getFocus()[0]).run(); + accessor.get(IInstantiationService).createInstance(ReplaceAllInFolderAction, tree, tree.getFocus()[0] as FolderMatch).run(); } } }); @@ -760,17 +760,6 @@ configurationRegistry.registerConfiguration({ default: false, description: nls.localize('search.showLineNumbers', "Controls whether to show line numbers for search results."), }, - 'searchRipgrep.enable': { - type: 'boolean', - default: false, - deprecationMessage: nls.localize('search.searchRipgrepEnableDeprecated', "Deprecated. Use \"search.runInExtensionHost\" instead"), - description: nls.localize('search.searchRipgrepEnable', "Whether to run search in the extension host") - }, - 'search.runInExtensionHost': { - type: 'boolean', - default: false, - description: nls.localize('search.runInExtensionHost', "Whether to run search in the extension host. Requires a restart to take effect.") - }, 'search.usePCRE2': { type: 'boolean', default: false, diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index 6659de4325..49343231d6 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -494,7 +494,7 @@ export abstract class AbstractSearchAndReplaceAction extends Action { export class RemoveAction extends AbstractSearchAndReplaceAction { - static LABEL = nls.localize('RemoveAction.label', "Dismiss"); + static readonly LABEL = nls.localize('RemoveAction.label', "Dismiss"); constructor( private viewer: WorkbenchObjectTree, diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 46f366fd42..96a27de58e 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -930,6 +930,8 @@ export class SearchView extends ViewletPanel { this.searchWidget.clear(); this.viewModel.cancelSearch(); this.updateActions(); + + aria.status(nls.localize('ariaSearchResultsClearStatus', "The search results have been cleared")); } cancelSearch(): boolean { @@ -1484,8 +1486,10 @@ export class SearchView extends ViewletPanel { this.messageDisposables.push(dom.addDisposableListener(openFolderLink, dom.EventType.CLICK, (e: MouseEvent) => { dom.EventHelper.stop(e, false); - const actionClass = env.isMacintosh ? OpenFileFolderAction : OpenFolderAction; - const action = this.instantiationService.createInstance(actionClass, actionClass.ID, actionClass.LABEL); + const action = env.isMacintosh ? + this.instantiationService.createInstance(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL) : + this.instantiationService.createInstance(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL); + this.actionRunner!.run(action).then(() => { action.dispose(); }, err => { diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index fe63960947..45a121caba 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -196,7 +196,7 @@ export class FileMatch extends Disposable implements IFileMatch { private _updateScheduler: RunOnceScheduler; private _modelDecorations: string[] = []; - constructor(private _query: IPatternInfo, private _previewOptions: ITextSearchPreviewOptions, private _maxResults: number, private _parent: FolderMatch, private rawMatch: IFileMatch, + constructor(private _query: IPatternInfo, private _previewOptions: ITextSearchPreviewOptions | undefined, private _maxResults: number | undefined, private _parent: FolderMatch, private rawMatch: IFileMatch, @IModelService private readonly modelService: IModelService, @IReplaceService private readonly replaceService: IReplaceService ) { super(); diff --git a/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts index b85ae30b42..e8f65e7b32 100644 --- a/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts +++ b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts @@ -23,8 +23,8 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; export class TabCompletionController implements editorCommon.IEditorContribution { - private static readonly ID = 'editor.tabCompletionController'; - static ContextKey = new RawContextKey('hasSnippetCompletions', undefined); + public static readonly ID = 'editor.tabCompletionController'; + static readonly ContextKey = new RawContextKey('hasSnippetCompletions', undefined); public static get(editor: ICodeEditor): TabCompletionController { return editor.getContribution(TabCompletionController.ID); @@ -50,10 +50,6 @@ export class TabCompletionController implements editorCommon.IEditorContribution this._update(); } - getId(): string { - return TabCompletionController.ID; - } - dispose(): void { dispose(this._configListener); dispose(this._selectionListener); @@ -143,7 +139,7 @@ export class TabCompletionController implements editorCommon.IEditorContribution } } -registerEditorContribution(TabCompletionController); +registerEditorContribution(TabCompletionController.ID, TabCompletionController); const TabCompletionCommand = EditorCommand.bindToContribution(TabCompletionController.get); 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 1dd7f8ce37..4c72713307 100644 --- a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts +++ b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts @@ -25,6 +25,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import * as perf from 'vs/base/common/performance'; import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { assertIsDefined } from 'vs/base/common/types'; class PartsSplash { @@ -59,6 +60,8 @@ class PartsSplash { if (e.affectsConfiguration('window.titleBarStyle')) { this._didChangeTitleBarStyle = true; this._savePartsSplash(); + } else if (e.affectsConfiguration('workbench.colorTheme') || e.affectsConfiguration('workbench.colorCustomizations')) { + this._savePartsSplash(); } }, this, this._disposables); } @@ -81,10 +84,10 @@ class PartsSplash { const layoutInfo = !this._shouldSaveLayoutInfo() ? undefined : { sideBarSide: this._layoutService.getSideBarPosition() === Position.RIGHT ? 'right' : 'left', editorPartMinWidth: DEFAULT_EDITOR_MIN_DIMENSIONS.width, - titleBarHeight: this._layoutService.isVisible(Parts.TITLEBAR_PART) ? getTotalHeight(this._layoutService.getContainer(Parts.TITLEBAR_PART)) : 0, - activityBarWidth: this._layoutService.isVisible(Parts.ACTIVITYBAR_PART) ? getTotalWidth(this._layoutService.getContainer(Parts.ACTIVITYBAR_PART)) : 0, - sideBarWidth: this._layoutService.isVisible(Parts.SIDEBAR_PART) ? getTotalWidth(this._layoutService.getContainer(Parts.SIDEBAR_PART)) : 0, - statusBarHeight: this._layoutService.isVisible(Parts.STATUSBAR_PART) ? getTotalHeight(this._layoutService.getContainer(Parts.STATUSBAR_PART)) : 0, + titleBarHeight: this._layoutService.isVisible(Parts.TITLEBAR_PART) ? getTotalHeight(assertIsDefined(this._layoutService.getContainer(Parts.TITLEBAR_PART))) : 0, + activityBarWidth: this._layoutService.isVisible(Parts.ACTIVITYBAR_PART) ? getTotalWidth(assertIsDefined(this._layoutService.getContainer(Parts.ACTIVITYBAR_PART))) : 0, + sideBarWidth: this._layoutService.isVisible(Parts.SIDEBAR_PART) ? getTotalWidth(assertIsDefined(this._layoutService.getContainer(Parts.SIDEBAR_PART))) : 0, + statusBarHeight: this._layoutService.isVisible(Parts.STATUSBAR_PART) ? getTotalHeight(assertIsDefined(this._layoutService.getContainer(Parts.STATUSBAR_PART))) : 0, }; this._textFileService.write( URI.file(join(this._envService.userDataPath, 'rapid_render.json')), diff --git a/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts b/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts index e2d8d58a8f..f37960a8c8 100644 --- a/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts +++ b/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts @@ -16,6 +16,7 @@ import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedPr import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/common/workspaceStats'; import { IWorkspaceInformation } from 'vs/platform/diagnostics/common/diagnostics'; import { IRequestService } from 'vs/platform/request/common/request'; +import { isWindows } from 'vs/base/common/platform'; const SshProtocolMatcher = /^([^@:]+@)?([^:]+):/; const SshUrlMatcher = /^([^@:]+@)?([^:]+):(.+)$/; @@ -153,6 +154,8 @@ export class WorkspaceStats implements IWorkbenchContribution { } private async report(): Promise { + // Windows-only Edition Event + this.reportWindowsEdition(); // Workspace Stats this.workspaceStatsService.getTags() @@ -167,6 +170,25 @@ export class WorkspaceStats implements IWorkbenchContribution { this.getWorkspaceInformation().then(stats => diagnosticsChannel.call('reportWorkspaceStats', stats)); } + async reportWindowsEdition(): Promise { + if (!isWindows) { + return; + } + + const Registry = await import('vscode-windows-registry'); + + let value; + try { + value = Registry.GetStringRegKey('HKEY_LOCAL_MACHINE', 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion', 'EditionID'); + } catch { } + + if (value === undefined) { + value = 'Unknown'; + } + + this.telemetryService.publicLog2<{ edition: string }, { edition: { classification: 'SystemMetaData', purpose: 'BusinessInsight' } }>('windowsEdition', { edition: value }); + } + private async getWorkspaceInformation(): Promise { const workspace = this.contextService.getWorkspace(); const state = this.contextService.getWorkbenchState(); diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 958847fd99..4d19dda33e 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -18,7 +18,7 @@ import * as strings from 'vs/base/common/strings'; 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 { LRUCache } from 'vs/base/common/map'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IMarkerService } from 'vs/platform/markers/common/markers'; @@ -33,7 +33,7 @@ import { IProgressService, IProgressOptions, ProgressLocation } from 'vs/platfor import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { INotificationService } from 'vs/platform/notification/common/notification'; +import { INotificationService, IPromptChoice } from 'vs/platform/notification/common/notification'; import { IDialogService, IConfirmationResult } from 'vs/platform/dialogs/common/dialogs'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -77,6 +77,10 @@ import { applyEdits } from 'vs/base/common/jsonEdit'; import { ITextEditor } from 'vs/workbench/common/editor'; import { ITextEditorSelection } from 'vs/platform/editor/common/editor'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { find } from 'vs/base/common/arrays'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; + +const QUICKOPEN_HISTORY_LIMIT_CONFIG = 'task.quickOpen.history'; export namespace ConfigureTaskAction { export const ID = 'workbench.action.tasks.configureTaskRunner'; @@ -126,6 +130,10 @@ interface TaskCustomizationTelemetryEvent { properties: string[]; } +function isWorkspaceFolder(folder: IWorkspace | IWorkspaceFolder): folder is IWorkspaceFolder { + return 'uri' in folder; +} + class TaskMap { private _store: Map = new Map(); @@ -133,20 +141,33 @@ class TaskMap { this._store.forEach(callback); } - public get(workspaceFolder: IWorkspaceFolder | string): Task[] { - let result: Task[] | undefined = Types.isString(workspaceFolder) ? this._store.get(workspaceFolder) : this._store.get(workspaceFolder.uri.toString()); + private getKey(workspaceFolder: IWorkspace | IWorkspaceFolder | string): string { + let key: string | undefined; + if (Types.isString(workspaceFolder)) { + key = workspaceFolder; + } else { + const uri: URI | null | undefined = isWorkspaceFolder(workspaceFolder) ? workspaceFolder.uri : workspaceFolder.configuration; + key = uri ? uri.toString() : ''; + } + return key; + } + + public get(workspaceFolder: IWorkspace | IWorkspaceFolder | string): Task[] { + const key = this.getKey(workspaceFolder); + let result: Task[] | undefined = this._store.get(key); if (!result) { result = []; - Types.isString(workspaceFolder) ? this._store.set(workspaceFolder, result) : this._store.set(workspaceFolder.uri.toString(), result); + this._store.set(key, result); } return result; } - public add(workspaceFolder: IWorkspaceFolder | string, ...task: Task[]): void { - let values = Types.isString(workspaceFolder) ? this._store.get(workspaceFolder) : this._store.get(workspaceFolder.uri.toString()); + public add(workspaceFolder: IWorkspace | IWorkspaceFolder | string, ...task: Task[]): void { + const key = this.getKey(workspaceFolder); + let values = this._store.get(key); if (!values) { values = []; - Types.isString(workspaceFolder) ? this._store.set(workspaceFolder, values) : this._store.set(workspaceFolder.uri.toString(), values); + this._store.set(key, values); } values.push(...task); } @@ -190,7 +211,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer protected _taskSystem?: ITaskSystem; protected _taskSystemListener?: IDisposable; - private _recentlyUsedTasks: LinkedMap | undefined; + private _recentlyUsedTasks: LRUCache | undefined; protected _taskRunningState: IContextKey; @@ -264,13 +285,17 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this.updateSetup(folderSetup); this.updateWorkspaceTasks(); })); - this._register(this.configurationService.onDidChangeConfiguration(() => { + this._register(Event.debounce(this.configurationService.onDidChangeConfiguration, () => { + return; + }, 1000)(() => { if (!this._taskSystem && !this._workspaceTasksPromise) { return; } if (!this._taskSystem || this._taskSystem instanceof TerminalTaskSystem) { this._outputChannel.clear(); } + + this.setTaskLRUCacheLimit(); this.updateWorkspaceTasks(TaskRunSource.ConfigurationChange); })); this._taskRunningState = TASK_RUNNING_STATE.bindTo(contextKeyService); @@ -290,7 +315,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer let entry: TaskQuickPickEntry | null | undefined; if (tasks && tasks.length > 0) { - entry = await this.showQuickPick(tasks, nls.localize('TaskService.pickBuildTaskForLabel', 'Select the build task')); + entry = await this.showQuickPick(tasks, nls.localize('TaskService.pickBuildTaskForLabel', 'Select the build task (there is no default build task defined)')); } let task: Task | undefined | null = entry ? entry.task : undefined; @@ -371,8 +396,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this.runConfigureDefaultTestTask(); }); - CommandsRegistry.registerCommand('workbench.action.tasks.showTasks', () => { - this.runShowTasks(); + CommandsRegistry.registerCommand('workbench.action.tasks.showTasks', async () => { + return this.runShowTasks(); }); CommandsRegistry.registerCommand('workbench.action.tasks.toggleProblems', () => { @@ -498,8 +523,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return this._taskSystem.customExecutionComplete(task, result); } - public getTask(folder: IWorkspaceFolder | string, identifier: string | TaskIdentifier, compareId: boolean = false): Promise { - const name = Types.isString(folder) ? folder : folder.name; + public getTask(folder: IWorkspace | IWorkspaceFolder | string, identifier: string | TaskIdentifier, compareId: boolean = false): Promise { + const name = Types.isString(folder) ? folder : isWorkspaceFolder(folder) ? folder.name : folder.configuration ? resources.basename(folder.configuration) : undefined; if (this.ignoredWorkspaceFolders.some(ignored => ignored.name === name)) { return Promise.reject(new Error(nls.localize('TaskServer.folderIgnored', 'The folder {0} is ignored since it uses task version 0.1.0', name))); } @@ -515,12 +540,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (!values) { return undefined; } - for (const task of values) { - if (task.matches(key, compareId)) { - return task; - } - } - return undefined; + return find(values, task => task.matches(key, compareId)); }); } @@ -573,11 +593,20 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return Promise.resolve(this._taskSystem.getActiveTasks()); } - public getRecentlyUsedTasks(): LinkedMap { + public getBusyTasks(): Promise { + if (!this._taskSystem) { + return Promise.resolve([]); + } + return Promise.resolve(this._taskSystem.getBusyTasks()); + } + + public getRecentlyUsedTasks(): LRUCache { if (this._recentlyUsedTasks) { return this._recentlyUsedTasks; } - this._recentlyUsedTasks = new LinkedMap(); + const quickOpenHistoryLimit = this.configurationService.getValue(QUICKOPEN_HISTORY_LIMIT_CONFIG); + this._recentlyUsedTasks = new LRUCache(quickOpenHistoryLimit); + let storageValue = this.storageService.get(AbstractTaskService.RecentlyUsedTasks_Key, StorageScope.WORKSPACE); if (storageValue) { try { @@ -594,8 +623,15 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return this._recentlyUsedTasks; } + private setTaskLRUCacheLimit() { + const quickOpenHistoryLimit = this.configurationService.getValue(QUICKOPEN_HISTORY_LIMIT_CONFIG); + if (this._recentlyUsedTasks) { + this._recentlyUsedTasks.limit = quickOpenHistoryLimit; + } + } + private setRecentlyUsedTask(key: string): void { - this.getRecentlyUsedTasks().set(key, key, Touch.AsOld); + this.getRecentlyUsedTasks().set(key, key); this.saveRecentlyUsedTasks(); } @@ -603,9 +639,14 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (!this._taskSystem || !this._recentlyUsedTasks) { return; } + const quickOpenHistoryLimit = this.configurationService.getValue(QUICKOPEN_HISTORY_LIMIT_CONFIG); + // setting history limit to 0 means no LRU sorting + if (quickOpenHistoryLimit === 0) { + return; + } let values = this._recentlyUsedTasks.values(); - if (values.length > 30) { - values = values.slice(0, 30); + if (values.length > quickOpenHistoryLimit) { + values = values.slice(0, quickOpenHistoryLimit); } this.storageService.store(AbstractTaskService.RecentlyUsedTasks_Key, JSON.stringify(values), StorageScope.WORKSPACE); } @@ -677,7 +718,18 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); } + private isProvideTasksEnabled(): boolean { + const settingValue = this.configurationService.getValue('task.autoDetect'); + return settingValue === true; + } + private shouldAttachProblemMatcher(task: Task): boolean { + const settingValue = this.configurationService.getValue('task.problemMatchers.neverPrompt'); + if (settingValue === true) { + return false; + } else if (task.type && Types.isStringArray(settingValue) && (settingValue.indexOf(task.type) >= 0)) { + return false; + } if (!this.canCustomize(task)) { return false; } @@ -983,7 +1035,12 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private getResourceForTask(task: CustomTask): URI { let uri = this.getResourceForKind(task._source.kind); if (!uri) { - uri = task.getWorkspaceFolder().toResource(task._source.config.file); + const taskFolder = task.getWorkspaceFolder(); + if (taskFolder) { + uri = taskFolder.toResource(task._source.config.file); + } else { + uri = this.workspaceFolders[0].uri; + } } return uri; } @@ -1034,8 +1091,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } }); let resolver: ITaskResolver = { - resolve: (workspaceFolder: IWorkspaceFolder, alias: string) => { - let data = resolverData.get(workspaceFolder.uri.toString()); + resolve: (uri: URI, alias: string) => { + let data = resolverData.get(uri.toString()); if (!data) { return undefined; } @@ -1066,7 +1123,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer { reevaluateOnRerun: true }, { identifier: id, - dependsOn: extensionTasks.map((extensionTask) => { return { workspaceFolder: extensionTask.getWorkspaceFolder()!, task: extensionTask._id }; }), + dependsOn: extensionTasks.map((extensionTask) => { return { uri: extensionTask.getWorkspaceFolder()!.uri, task: extensionTask._id }; }), name: id, } ); @@ -1099,9 +1156,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } }); + return { - resolve: (workspaceFolder: IWorkspaceFolder, identifier: string | TaskIdentifier | undefined) => { - let data = resolverData.get(workspaceFolder.uri.toString()); + resolve: (uri: URI, identifier: string | TaskIdentifier | undefined) => { + let data = uri ? resolverData.get(uri.toString()) : undefined; if (!data || !identifier) { return undefined; } @@ -1211,6 +1269,39 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer protected abstract getTaskSystem(): ITaskSystem; + private async provideTasksWithWarning(provider: ITaskProvider, type: string, validTypes: IStringDictionary): Promise { + return new Promise(async (resolve, reject) => { + let isDone = false; + provider.provideTasks(validTypes).then((value) => { + isDone = true; + resolve(value); + }, (e) => { + isDone = true; + reject(e); + }); + let settingValue: boolean | string[] = this.configurationService.getValue('task.slowProviderWarning'); + if ((settingValue === true) || (Types.isStringArray(settingValue) && (settingValue.indexOf(type) < 0))) { + setTimeout(() => { + if (!isDone) { + const settings: IPromptChoice = { label: nls.localize('TaskSystem.slowProvider.settings', "Settings"), run: () => this.preferencesService.openSettings(false, undefined) }; + const disableAll: IPromptChoice = { label: nls.localize('TaskSystem.slowProvider.disableAll', "Disable All"), run: () => this.configurationService.updateValue('task.autoDetect', false) }; + const dontShow: IPromptChoice = { + label: nls.localize('TaskSystem.slowProvider.dontShow', "Don't warn again for {0} tasks", type), run: () => { + if (!Types.isStringArray(settingValue)) { + settingValue = []; + } + settingValue.push(type); + return this.configurationService.updateValue('task.slowProviderWarning', settingValue); + } + }; + this.notificationService.prompt(Severity.Warning, nls.localize('TaskSystem.slowProvider', "The {0} task provider is slow. The extension that provides {0} tasks may provide a setting to disable it, or you can disable all tasks providers", type), + [settings, disableAll, dontShow]); + } + }, 1000); + } + }); + } + private getGroupedTasks(type?: string): Promise { return Promise.all([this.extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'), TaskDefinitionRegistry.onReady()]).then(() => { let validTypes: IStringDictionary = Object.create(null); @@ -1245,11 +1336,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } }; - if (this.schemaVersion === JsonSchemaVersion.V2_0_0 && this._providers.size > 0) { + if (this.isProvideTasksEnabled() && (this.schemaVersion === JsonSchemaVersion.V2_0_0) && (this._providers.size > 0)) { for (const [handle, provider] of this._providers) { if ((type === undefined) || (type === this._providerTypes.get(handle))) { counter++; - provider.provideTasks(validTypes).then(done, error); + this.provideTasksWithWarning(provider, this._providerTypes.get(handle)!, validTypes).then(done, error); } } } else { @@ -1464,7 +1555,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return ProblemMatcherRegistry.onReady().then(async (): Promise => { let taskSystemInfo: TaskSystemInfo | undefined = this._taskSystemInfos.get(workspaceFolder.uri.scheme); let problemReporter = new ProblemReporter(this._outputChannel); - let parseResult = TaskConfig.parse(workspaceFolder, this._workspace, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, workspaceFolderConfiguration.config!, problemReporter, TaskConfig.TaskConfigSource.TasksJson); + let parseResult = TaskConfig.parse(workspaceFolder, undefined, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, workspaceFolderConfiguration.config!, problemReporter, TaskConfig.TaskConfigSource.TasksJson); let hasErrors = false; if (!parseResult.validationStatus.isOK()) { hasErrors = true; @@ -1834,7 +1925,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer taskMap[key] = task; } }); - recentlyUsedTasks.keys().forEach(key => { + recentlyUsedTasks.keys().reverse().forEach(key => { let task = taskMap[key]; if (task) { recent.push(task); @@ -1929,7 +2020,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer let resolver = this.createResolver(grouped); let folders = this.contextService.getWorkspace().folders; for (let folder of folders) { - let task = resolver.resolve(folder, identifier); + let task = resolver.resolve(folder.uri, identifier); if (task) { this.run(task).then(undefined, reason => { // eat the error, it has already been surfaced to the user and we don't care about it here @@ -2309,9 +2400,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer let createLabel = nls.localize('TaskService.createJsonFile', 'Create tasks.json file from template'); let openLabel = nls.localize('TaskService.openJsonFile', 'Open tasks.json file'); + const tokenSource = new CancellationTokenSource(); + const cancellationToken: CancellationToken = tokenSource.token; + type EntryType = (IQuickPickItem & { task: Task; }) | (IQuickPickItem & { folder: IWorkspaceFolder; }); let entries = Promise.all(stats).then((stats) => { return taskPromise.then((taskMap) => { - type EntryType = (IQuickPickItem & { task: Task; }) | (IQuickPickItem & { folder: IWorkspaceFolder; }); let entries: QuickPickInput[] = []; if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY) { let tasks = taskMap.all(); @@ -2355,13 +2448,23 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer index++; } } + if (entries.length === 1) { + tokenSource.cancel(); + } return entries; }); }); this.quickInputService.pick(entries, - { placeHolder: nls.localize('TaskService.pickTask', 'Select a task to configure') }). - then((selection) => { + { placeHolder: nls.localize('TaskService.pickTask', 'Select a task to configure') }, cancellationToken). + then(async (selection) => { + if (cancellationToken.isCancellationRequested) { + // canceled when there's only one task + const task = (await entries)[0]; + if ((task).task) { + selection = task; + } + } if (!selection) { return; } @@ -2474,23 +2577,28 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } - public runShowTasks(): void { + public async runShowTasks(): Promise { if (!this.canRunCommand()) { return; } - this.showQuickPick(this.getActiveTasks(), - nls.localize('TaskService.pickShowTask', 'Select the task to show its output'), - { - label: nls.localize('TaskService.noTaskIsRunning', 'No task is running'), - task: null - }, - false, true - ).then((entry) => { - let task: Task | undefined | null = entry ? entry.task : undefined; - if (task === undefined || task === null) { - return; - } - this._taskSystem!.revealTask(task); - }); + const activeTasks: Task[] = await this.getActiveTasks(); + if (activeTasks.length === 1) { + this._taskSystem!.revealTask(activeTasks[0]); + } else { + this.showQuickPick(this.getActiveTasks(), + nls.localize('TaskService.pickShowTask', 'Select the task to show its output'), + { + label: nls.localize('TaskService.noTaskIsRunning', 'No task is running'), + task: null + }, + false, true + ).then((entry) => { + let task: Task | undefined | null = entry ? entry.task : undefined; + if (task === undefined || task === null) { + return; + } + this._taskSystem!.revealTask(task); + }); + } } } diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index bdff834733..87dc49ef91 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -35,6 +35,11 @@ import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/wor import { RunAutomaticTasks, ManageAutomaticTaskRunning } from 'vs/workbench/contrib/tasks/browser/runAutomaticTasks'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import schemaVersion1 from '../common/jsonSchema_v1'; +import schemaVersion2, { updateProblemMatchers } from '../common/jsonSchema_v2'; +import { AbstractTaskService, ConfigureTaskAction } from 'vs/workbench/contrib/tasks/browser/abstractTaskService'; +import { tasksSchemaId } from 'vs/workbench/services/configuration/common/configuration'; +import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; let tasksCategory = nls.localize('tasksCategory', "Tasks"); @@ -101,7 +106,7 @@ export class TaskStatusBarContributions extends Disposable implements IWorkbench } if (promise && (event.kind === TaskEventKind.Active) && (this.activeTasksCount === 1)) { - this.progressService.withProgress({ location: ProgressLocation.Window }, progress => { + this.progressService.withProgress({ location: ProgressLocation.Window, command: 'workbench.action.tasks.showTasks' }, progress => { progress.report({ message: nls.localize('building', 'Building...') }); return promise!; }).then(() => { @@ -288,10 +293,6 @@ let schema: IJSONSchema = { } }; -import schemaVersion1 from '../common/jsonSchema_v1'; -import schemaVersion2, { updateProblemMatchers } from '../common/jsonSchema_v2'; -import { AbstractTaskService, ConfigureTaskAction } from 'vs/workbench/contrib/tasks/browser/abstractTaskService'; -import { tasksSchemaId } from 'vs/workbench/services/configuration/common/configuration'; schema.definitions = { ...schemaVersion1.definitions, ...schemaVersion2.definitions, @@ -305,3 +306,57 @@ ProblemMatcherRegistry.onMatcherChanged(() => { updateProblemMatchers(); jsonRegistry.notifySchemaChanged(tasksSchemaId); }); + +const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); +configurationRegistry.registerConfiguration({ + id: 'task', + order: 100, + title: nls.localize('tasksConfigurationTitle', "Tasks"), + type: 'object', + properties: { + 'task.problemMatchers.neverPrompt': { + markdownDescription: nls.localize('task.problemMatchers.neverPrompt', "Configures whether to show the problem matcher prompt when running a task. Set to `true` to never promp, or use an array of task types to turn off prompting only for specific task types."), + 'oneOf': [ + { + type: 'boolean', + markdownDescription: nls.localize('task.problemMatchers.neverPrompt.boolean', 'Sets problem matcher prompting behavior for all tasks.') + }, + { + type: 'array', + items: { + type: 'string', + markdownDescription: nls.localize('task.problemMatchers.neverPrompt.array', 'An array of task types to never prompt for problem matchers on.') + } + } + ], + default: false + }, + 'task.autoDetect': { + markdownDescription: nls.localize('task.autoDetect', "Controls enablement of `provideTasks` for all task provider extension. If the Tasks: Run Task command is slow, disabling auto detect for task providers may help. Individual extensions my provide settings to disabled auto detection."), + type: 'boolean', + default: true + }, + 'task.slowProviderWarning': { + markdownDescription: nls.localize('task.slowProviderWarning', "Configures whether a warning is shown when a provider is slow"), + 'oneOf': [ + { + type: 'boolean', + markdownDescription: nls.localize('task.slowProviderWarning.boolean', 'Sets the slow provider warning for all tasks.') + }, + { + type: 'array', + items: { + type: 'string', + markdownDescription: nls.localize('task.slowProviderWarning.array', 'An array of task types to never show the slow provider warning.') + } + } + ], + default: true + }, + 'task.quickOpen.history': { + markdownDescription: nls.localize('task.quickOpen.history', "Controls the number of recent items tracked in task quick open dialog."), + type: 'number', + default: 30, minimum: 0, maximum: 30 + }, + } +}); diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index b73716c6d2..39d9a2f945 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -113,7 +113,7 @@ export class TerminalTaskSystem implements ITaskSystem { public static TelemetryEventName: string = 'taskService'; - private static ProcessVarName = '__process__'; + private static readonly ProcessVarName = '__process__'; private static shellQuotes: IStringDictionary = { 'cmd': { @@ -152,6 +152,7 @@ export class TerminalTaskSystem implements ITaskSystem { }; private activeTasks: IStringDictionary; + private busyTasks: IStringDictionary; private terminals: IStringDictionary; private idleTaskTerminals: LinkedMap; private sameTaskTerminals: IStringDictionary; @@ -180,6 +181,7 @@ export class TerminalTaskSystem implements ITaskSystem { ) { this.activeTasks = Object.create(null); + this.busyTasks = Object.create(null); this.terminals = Object.create(null); this.idleTaskTerminals = new LinkedMap(); this.sameTaskTerminals = Object.create(null); @@ -280,6 +282,10 @@ export class TerminalTaskSystem implements ITaskSystem { return Object.keys(this.activeTasks).map(key => this.activeTasks[key].task); } + public getBusyTasks(): Task[] { + return Object.keys(this.busyTasks).map(key => this.busyTasks[key]); + } + public customExecutionComplete(task: Task, result: number): Promise { let activeTerminal = this.activeTasks[task.getMapKey()]; if (!activeTerminal) { @@ -341,7 +347,7 @@ export class TerminalTaskSystem implements ITaskSystem { let promises: Promise[] = []; if (task.configurationProperties.dependsOn) { for (const dependency of task.configurationProperties.dependsOn) { - let dependencyTask = resolver.resolve(dependency.workspaceFolder, dependency.task!); + let dependencyTask = resolver.resolve(dependency.uri, dependency.task!); if (dependencyTask) { let key = dependencyTask.getMapKey(); let promise = this.activeTasks[key] ? this.activeTasks[key].promise : undefined; @@ -357,7 +363,7 @@ export class TerminalTaskSystem implements ITaskSystem { this.log(nls.localize('dependencyFailed', 'Couldn\'t resolve dependent task \'{0}\' in workspace folder \'{1}\'', Types.isString(dependency.task) ? dependency.task : JSON.stringify(dependency.task, undefined, 0), - dependency.workspaceFolder.name + dependency.uri.toString() )); this.showOutput(); } @@ -462,7 +468,14 @@ export class TerminalTaskSystem implements ITaskSystem { } private executeCommand(task: CustomTask | ContributedTask, trigger: string): Promise { - const workspaceFolder = this.currentTask.workspaceFolder = task.getWorkspaceFolder(); + const taskWorkspaceFolder = task.getWorkspaceFolder(); + let workspaceFolder: IWorkspaceFolder | undefined; + if (taskWorkspaceFolder) { + workspaceFolder = this.currentTask.workspaceFolder = taskWorkspaceFolder; + } else { + const folders = this.contextService.getWorkspace().folders; + workspaceFolder = folders.length > 0 ? folders[0] : undefined; + } const systemInfo: TaskSystemInfo | undefined = this.currentTask.systemInfo = workspaceFolder ? this.taskSystemInfoResolver(workspaceFolder) : undefined; let variables = new Set(); @@ -470,11 +483,13 @@ export class TerminalTaskSystem implements ITaskSystem { const resolvedVariables = this.resolveVariablesFromSet(systemInfo, workspaceFolder, task, variables); return resolvedVariables.then((resolvedVariables) => { - const isCustomExecution = (task.command.runtime === RuntimeType.CustomExecution2); + const isCustomExecution = (task.command.runtime === RuntimeType.CustomExecution); if (resolvedVariables && (task.command !== undefined) && task.command.runtime && (isCustomExecution || (task.command.name !== undefined))) { this.currentTask.resolvedVariables = resolvedVariables; return this.executeInTerminal(task, trigger, new VariableResolver(workspaceFolder, systemInfo, resolvedVariables.variables, this.configurationResolverService), workspaceFolder); } else { + // Allows the taskExecutions array to be updated in the extension host + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.End, task)); return Promise.resolve({ exitCode: 0 }); } }, reason => { @@ -520,14 +535,23 @@ export class TerminalTaskSystem implements ITaskSystem { if (task.configurationProperties.isBackground) { const problemMatchers = this.resolveMatchers(resolver, task.configurationProperties.problemMatchers); let watchingProblemMatcher = new WatchingProblemCollector(problemMatchers, this.markerService, this.modelService, this.fileService); + if ((problemMatchers.length > 0) && !watchingProblemMatcher.isWatching()) { + this.appendOutput(nls.localize('TerminalTaskSystem.nonWatchingMatcher', 'Task {0} is a background task but uses a problem matcher without a background pattern', task._label)); + this.showOutput(); + } const toDispose = new DisposableStore(); let eventCounter: number = 0; + const mapKey = task.getMapKey(); toDispose.add(watchingProblemMatcher.onDidStateChange((event) => { if (event.kind === ProblemCollectorEventKind.BackgroundProcessingBegins) { eventCounter++; + this.busyTasks[mapKey] = task; this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Active, task)); } else if (event.kind === ProblemCollectorEventKind.BackgroundProcessingEnds) { eventCounter--; + if (this.busyTasks[mapKey]) { + delete this.busyTasks[mapKey]; + } this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Inactive, task)); if (eventCounter === 0) { if ((watchingProblemMatcher.numberOfMatches > 0) && watchingProblemMatcher.maxMarkerSeverity && @@ -586,6 +610,9 @@ export class TerminalTaskSystem implements ITaskSystem { onData.dispose(); onExit.dispose(); let key = task.getMapKey(); + if (this.busyTasks[mapKey]) { + delete this.busyTasks[mapKey]; + } delete this.activeTasks[key]; this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Changed)); if (exitCode !== undefined) { @@ -645,6 +672,8 @@ export class TerminalTaskSystem implements ITaskSystem { // The process never got ready. Need to think how to handle this. }); this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task, terminal.id)); + const mapKey = task.getMapKey(); + this.busyTasks[mapKey] = task; 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, ProblemHandlingStrategy.Clean, this.fileService); @@ -698,6 +727,9 @@ export class TerminalTaskSystem implements ITaskSystem { } this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode)); + if (this.busyTasks[mapKey]) { + delete this.busyTasks[mapKey]; + } this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Inactive, task)); this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.End, task)); resolve({ exitCode }); @@ -850,7 +882,7 @@ export class TerminalTaskSystem implements ITaskSystem { } } } else { - let commandExecutable = (task.command.runtime !== RuntimeType.CustomExecution2) ? CommandString.value(command) : undefined; + let commandExecutable = (task.command.runtime !== RuntimeType.CustomExecution) ? CommandString.value(command) : undefined; let executable = !isShellCommand ? this.resolveVariable(variableResolver, '${' + TerminalTaskSystem.ProcessVarName + '}') : commandExecutable; @@ -921,7 +953,7 @@ export class TerminalTaskSystem implements ITaskSystem { let args: CommandString[] | undefined; let launchConfigs: IShellLaunchConfig | undefined; - if (task.command.runtime === RuntimeType.CustomExecution2) { + if (task.command.runtime === RuntimeType.CustomExecution) { this.currentTask.shellLaunchConfig = launchConfigs = { isExtensionTerminal: true, waitOnExit, @@ -976,6 +1008,7 @@ export class TerminalTaskSystem implements ITaskSystem { throw new Error('Task shell launch configuration should not be undefined here.'); } + terminalToReuse.terminal.scrollToBottom(); terminalToReuse.terminal.reuseTerminal(launchConfigs); if (task.command.presentation && task.command.presentation.clear) { @@ -1016,7 +1049,11 @@ export class TerminalTaskSystem implements ITaskSystem { // This can happen if the terminal wasn't shutdown with an "immediate" flag and is expected. // For correct terminal re-use, the task needs to be deleted immediately. // Note that this shouldn't be a problem anymore since user initiated terminal kills are now immediate. - delete this.activeTasks[task.getMapKey()]; + const mapKey = task.getMapKey(); + delete this.activeTasks[mapKey]; + if (this.busyTasks[mapKey]) { + delete this.busyTasks[mapKey]; + } } }); this.terminals[terminalKey] = { terminal: result, lastTask: taskKey, group }; @@ -1142,7 +1179,7 @@ export class TerminalTaskSystem implements ITaskSystem { private collectCommandVariables(variables: Set, command: CommandConfiguration, task: CustomTask | ContributedTask): void { // The custom execution should have everything it needs already as it provided // the callback. - if (command.runtime === RuntimeType.CustomExecution2) { + if (command.runtime === RuntimeType.CustomExecution) { return; } diff --git a/src/vs/workbench/contrib/tasks/common/jsonSchemaCommon.ts b/src/vs/workbench/contrib/tasks/common/jsonSchemaCommon.ts index 452c342c5d..1340ffd64d 100644 --- a/src/vs/workbench/contrib/tasks/common/jsonSchemaCommon.ts +++ b/src/vs/workbench/contrib/tasks/common/jsonSchemaCommon.ts @@ -115,16 +115,52 @@ const schema: IJSONSchema = { $ref: '#/definitions/options' }, windows: { - $ref: '#/definitions/commandConfiguration', - description: nls.localize('JsonSchema.tasks.windows', 'Windows specific command configuration') + anyOf: [ + { + $ref: '#/definitions/commandConfiguration', + description: nls.localize('JsonSchema.tasks.windows', 'Windows specific command configuration'), + }, + { + properties: { + problemMatcher: { + $ref: '#/definitions/problemMatcherType', + description: nls.localize('JsonSchema.tasks.matchers', 'The problem matcher(s) to use. Can either be a string or a problem matcher definition or an array of strings and problem matchers.') + } + } + } + ] }, osx: { - $ref: '#/definitions/commandConfiguration', - description: nls.localize('JsonSchema.tasks.mac', 'Mac specific command configuration') + anyOf: [ + { + $ref: '#/definitions/commandConfiguration', + description: nls.localize('JsonSchema.tasks.mac', 'Mac specific command configuration') + }, + { + properties: { + problemMatcher: { + $ref: '#/definitions/problemMatcherType', + description: nls.localize('JsonSchema.tasks.matchers', 'The problem matcher(s) to use. Can either be a string or a problem matcher definition or an array of strings and problem matchers.') + } + } + } + ] }, linux: { - $ref: '#/definitions/commandConfiguration', - description: nls.localize('JsonSchema.tasks.linux', 'Linux specific command configuration') + anyOf: [ + { + $ref: '#/definitions/commandConfiguration', + description: nls.localize('JsonSchema.tasks.linux', 'Linux specific command configuration') + }, + { + properties: { + problemMatcher: { + $ref: '#/definitions/problemMatcherType', + description: nls.localize('JsonSchema.tasks.matchers', 'The problem matcher(s) to use. Can either be a string or a problem matcher definition or an array of strings and problem matchers.') + } + } + } + ] }, suppressTaskName: { type: 'boolean', @@ -241,4 +277,4 @@ const schema: IJSONSchema = { } }; -export default schema; \ No newline at end of file +export default schema; diff --git a/src/vs/workbench/contrib/tasks/common/problemCollectors.ts b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts index cf1d3e370e..658a5e5b45 100644 --- a/src/vs/workbench/contrib/tasks/common/problemCollectors.ts +++ b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts @@ -524,4 +524,8 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement }); super.done(); } + + public isWatching(): boolean { + return this.backgroundPatterns.length > 0; + } } diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index d8d0877b47..d63689eeba 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -1115,6 +1115,20 @@ namespace ProblemMatcherConverter { return result; } + export function fromWithOsConfig(this: void, external: ConfigurationProperties & { [key: string]: any; }, context: ParseContext): ProblemMatcher[] | undefined { + let result: ProblemMatcher[] | undefined = undefined; + if (external.windows && external.windows.problemMatcher && context.platform === Platform.Windows) { + result = from(external.windows.problemMatcher, context); + } else if (external.osx && external.osx.problemMatcher && context.platform === Platform.Mac) { + result = from(external.osx.problemMatcher, context); + } else if (external.linux && external.linux.problemMatcher && context.platform === Platform.Linux) { + result = from(external.linux.problemMatcher, context); + } else if (external.problemMatcher) { + result = from(external.problemMatcher, context); + } + return result; + } + export function from(this: void, config: ProblemMatcherConfig.ProblemMatcherType | undefined, context: ParseContext): ProblemMatcher[] { let result: ProblemMatcher[] = []; if (config === undefined) { @@ -1212,9 +1226,12 @@ namespace GroupKind { namespace TaskDependency { export function from(this: void, external: string | TaskIdentifier, context: ParseContext): Tasks.TaskDependency | undefined { if (Types.isString(external)) { - return { workspaceFolder: context.workspaceFolder, task: external }; + return { uri: context.workspace && context.workspace.configuration ? context.workspace.configuration : context.workspaceFolder.uri, task: external }; } else if (TaskIdentifier.is(external)) { - return { workspaceFolder: context.workspaceFolder, task: Tasks.TaskDefinition.createTaskIdentifier(external as Tasks.TaskIdentifier, context.problemReporter) }; + return { + uri: context.workspace && context.workspace.configuration ? context.workspace.configuration : context.workspaceFolder.uri, + task: Tasks.TaskDefinition.createTaskIdentifier(external as Tasks.TaskIdentifier, context.problemReporter) + }; } else { return undefined; } @@ -1305,8 +1322,9 @@ namespace ConfigurationProperties { if (includeCommandOptions && (external.options !== undefined)) { result.options = CommandOptions.from(external.options, context); } - if (external.problemMatcher) { - result.problemMatchers = ProblemMatcherConverter.from(external.problemMatcher, context); + const configProblemMatcher = ProblemMatcherConverter.fromWithOsConfig(external, context); + if (configProblemMatcher !== undefined) { + result.problemMatchers = configProblemMatcher; } return isEmpty(result) ? undefined : result; } @@ -2047,80 +2065,3 @@ export function createCustomTask(contributedTask: Tasks.ContributedTask, configu return CustomTask.createCustomTask(contributedTask, configuredProps); } -/* -class VersionConverter { - constructor(private problemReporter: IProblemReporter) { - } - - public convert(fromConfig: ExternalTaskRunnerConfiguration): ExternalTaskRunnerConfiguration { - let result: ExternalTaskRunnerConfiguration; - result.version = '2.0.0'; - if (Array.isArray(fromConfig.tasks)) { - - } else { - result.tasks = []; - } - - - return result; - } - - private convertGlobalTask(fromConfig: ExternalTaskRunnerConfiguration): TaskDescription { - let command: string = this.getGlobalCommand(fromConfig); - if (!command) { - this.problemReporter.error(nls.localize('Converter.noGlobalName', 'No global command specified. Can\'t convert to 2.0.0 version.')); - return undefined; - } - let result: TaskDescription = { - taskName: command - }; - if (fromConfig.isShellCommand) { - result.type = 'shell'; - } else { - result.type = 'process'; - result.args = fromConfig.args; - } - if (fromConfig.) - - return result; - } - - private getGlobalCommand(fromConfig: ExternalTaskRunnerConfiguration): string { - if (fromConfig.command) { - return fromConfig.command; - } else if (fromConfig.windows && fromConfig.windows.command) { - return fromConfig.windows.command; - } else if (fromConfig.osx && fromConfig.osx.command) { - return fromConfig.osx.command; - } else if (fromConfig.linux && fromConfig.linux.command) { - return fromConfig.linux.command; - } else { - return undefined; - } - } - - private createCommandLine(command: string, args: string[], isWindows: boolean): string { - let result: string[]; - let commandHasSpace = false; - let argHasSpace = false; - if (TaskDescription.hasUnescapedSpaces(command)) { - result.push(`"${command}"`); - commandHasSpace = true; - } else { - result.push(command); - } - if (args) { - for (let arg of args) { - if (TaskDescription.hasUnescapedSpaces(arg)) { - result.push(`"${arg}"`); - argHasSpace= true; - } else { - result.push(arg); - } - } - } - return result.join(' '); - } - -} -*/ diff --git a/src/vs/workbench/contrib/tasks/common/taskService.ts b/src/vs/workbench/contrib/tasks/common/taskService.ts index ec76f2afb2..b585517032 100644 --- a/src/vs/workbench/contrib/tasks/common/taskService.ts +++ b/src/vs/workbench/contrib/tasks/common/taskService.ts @@ -9,7 +9,7 @@ import { LinkedMap } from 'vs/base/common/map'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { Task, ContributedTask, CustomTask, TaskSet, TaskSorter, TaskEvent, TaskIdentifier, ConfiguringTask, TaskRunSource } from 'vs/workbench/contrib/tasks/common/tasks'; import { ITaskSummary, TaskTerminateResponse, TaskSystemInfo } from 'vs/workbench/contrib/tasks/common/taskSystem'; import { IStringDictionary } from 'vs/base/common/collections'; @@ -62,6 +62,7 @@ export interface ITaskService { inTerminal(): boolean; isActive(): Promise; getActiveTasks(): Promise; + getBusyTasks(): Promise; restart(task: Task): void; terminate(task: Task): Promise; terminateAll(): Promise; @@ -70,7 +71,7 @@ export interface ITaskService { /** * @param alias The task's name, label or defined identifier. */ - getTask(workspaceFolder: IWorkspaceFolder | string, alias: string | TaskIdentifier, compareId?: boolean): Promise; + getTask(workspaceFolder: IWorkspace | IWorkspaceFolder | string, alias: string | TaskIdentifier, compareId?: boolean): Promise; getTasksForGroup(group: string): Promise; getRecentlyUsedTasks(): LinkedMap; createSorter(): TaskSorter; diff --git a/src/vs/workbench/contrib/tasks/common/taskSystem.ts b/src/vs/workbench/contrib/tasks/common/taskSystem.ts index 8055f18071..9a8edc2585 100644 --- a/src/vs/workbench/contrib/tasks/common/taskSystem.ts +++ b/src/vs/workbench/contrib/tasks/common/taskSystem.ts @@ -93,7 +93,7 @@ export interface ITaskExecuteResult { } export interface ITaskResolver { - resolve(workspaceFolder: IWorkspaceFolder, identifier: string | KeyedTaskIdentifier | undefined): Task | undefined; + resolve(uri: URI, identifier: string | KeyedTaskIdentifier | undefined): Task | undefined; } export interface TaskTerminateResponse extends TerminateResponse { @@ -133,6 +133,7 @@ export interface ITaskSystem { isActive(): Promise; isActiveSync(): boolean; getActiveTasks(): Task[]; + getBusyTasks(): Task[]; canAutoTerminate(): boolean; terminate(task: Task): Promise; terminateAll(): Promise; diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index 54309043ce..173ec046e3 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -8,7 +8,7 @@ import * as Types from 'vs/base/common/types'; import * as resources from 'vs/base/common/resources'; import { IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import * as Objects from 'vs/base/common/objects'; -import { UriComponents } from 'vs/base/common/uri'; +import { UriComponents, URI } from 'vs/base/common/uri'; import { ProblemMatcher } from 'vs/workbench/contrib/tasks/common/problemMatcher'; import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; @@ -274,7 +274,7 @@ export namespace PresentationOptions { export enum RuntimeType { Shell = 1, Process = 2, - CustomExecution2 = 3 + CustomExecution = 3 } export namespace RuntimeType { @@ -284,8 +284,8 @@ export namespace RuntimeType { return RuntimeType.Shell; case 'process': return RuntimeType.Process; - case 'customExecution2': - return RuntimeType.CustomExecution2; + case 'customExecution': + return RuntimeType.CustomExecution; default: return RuntimeType.Process; } @@ -380,7 +380,7 @@ export namespace TaskSourceKind { } export interface TaskSourceConfigElement { - workspaceFolder: IWorkspaceFolder; + workspaceFolder?: IWorkspaceFolder; workspace?: IWorkspace; file: string; index: number; @@ -438,7 +438,7 @@ export interface KeyedTaskIdentifier extends TaskIdentifier { } export interface TaskDependency { - workspaceFolder: IWorkspaceFolder; + uri: URI; task: string | KeyedTaskIdentifier | undefined; } @@ -680,8 +680,8 @@ export class CustomTask extends CommonTask { type = 'process'; break; - case RuntimeType.CustomExecution2: - type = 'customExecution2'; + case RuntimeType.CustomExecution: + type = 'customExecution'; break; case undefined: @@ -728,7 +728,7 @@ export class CustomTask extends CommonTask { return JSON.stringify(key); } - public getWorkspaceFolder(): IWorkspaceFolder { + public getWorkspaceFolder(): IWorkspaceFolder | undefined { return this._source.config.workspaceFolder; } diff --git a/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts b/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts index aa4a7c69dd..f47fb1fbdb 100644 --- a/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts @@ -90,6 +90,10 @@ export class ProcessTaskSystem implements ITaskSystem { return result; } + public getBusyTasks(): Task[] { + return this.getActiveTasks(); + } + public run(task: Task): ITaskExecuteResult { if (this.activeTask) { return { kind: TaskExecuteKind.Active, task, active: { same: this.activeTask._id === task._id, background: this.activeTask.configurationProperties.isBackground! }, promise: this.activeTaskPromise! }; diff --git a/src/vs/workbench/contrib/terminal/browser/addons/navigationModeAddon.ts b/src/vs/workbench/contrib/terminal/browser/addons/navigationModeAddon.ts index cac8c3a990..f0cc177b53 100644 --- a/src/vs/workbench/contrib/terminal/browser/addons/navigationModeAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/addons/navigationModeAddon.ts @@ -30,7 +30,7 @@ export class NavigationModeAddon implements INavigationMode, ITerminalAddon { } focusPreviousLine(): void { - if (!this._terminal) { + if (!this._terminal || !this._terminal.element) { return; } @@ -73,7 +73,7 @@ export class NavigationModeAddon implements INavigationMode, ITerminalAddon { } focusNextLine(): void { - if (!this._terminal) { + if (!this._terminal || !this._terminal.element) { return; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 8c30fec6d6..c8797854d4 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -251,6 +251,11 @@ configurationRegistry.registerConfiguration({ }, default: [] }, + 'terminal.integrated.allowChords': { + markdownDescription: nls.localize('terminal.integrated.allowChords', "Whether or not to allow chord keybindings in the terminal. Note that when this is true and the keystroke results in a chord it will bypass `terminal.integrated.commandsToSkipShell`, setting this to false is particularly useful when you want ctrl+k to go to your shell (not VS Code)."), + type: 'boolean', + default: true + }, 'terminal.integrated.inheritEnv': { markdownDescription: nls.localize('terminal.integrated.inheritEnv', "Whether new shells should inherit their environment from Azure Data Studio. This is not supported on Windows."), // {{SQL CARBON EDIT}} Change product name to ADS type: 'boolean', diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 0ba28b304d..4994514eb1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -10,7 +10,6 @@ import { IWindowsShellHelper, ITerminalConfigHelper, ITerminalChildProcess, IShe import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProcessEnvironment, Platform } from 'vs/base/common/platform'; import { Event } from 'vs/base/common/event'; -import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IDisposable } from 'vs/base/common/lifecycle'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { URI } from 'vs/base/common/uri'; @@ -65,7 +64,7 @@ export interface ITerminalTab { setVisible(visible: boolean): void; layout(width: number, height: number): void; addDisposable(disposable: IDisposable): void; - split(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance | undefined; + split(shellLaunchConfig: IShellLaunchConfig): ITerminalInstance; } export interface ITerminalService { @@ -227,7 +226,7 @@ export interface ITerminalInstance { * is the processes' exit code, an exit code of null means the process was killed as a result of * the ITerminalInstance being disposed. */ - onExit: Event; + onExit: Event; processReady: Promise; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 5f56cb9264..a2d7d5d52e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -25,7 +25,7 @@ import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderB import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/terminalWidgetManager'; -import { IShellLaunchConfig, ITerminalDimensions, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_PANEL_ID, IWindowsShellHelper, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE, SHELL_CWD_INVALID_EXIT_CODE, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IShellLaunchConfig, ITerminalDimensions, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_PANEL_ID, IWindowsShellHelper, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE, SHELL_CWD_INVALID_EXIT_CODE, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, TERMINAL_COMMAND_ID, LEGACY_CONSOLE_MODE_EXIT_CODE } from 'vs/workbench/contrib/terminal/common/terminal'; import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_FOREGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalLinkHandler } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler'; @@ -38,6 +38,7 @@ import { SearchAddon, ISearchOptions } from 'xterm-addon-search'; import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon'; import { NavigationModeAddon } from 'vs/workbench/contrib/terminal/browser/addons/navigationModeAddon'; import { XTermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private'; +import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; // How long in milliseconds should an average frame take to render for a notification to appear // which suggests the fallback DOM-based renderer @@ -232,8 +233,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { public get commandTracker(): CommandTrackerAddon | undefined { return this._commandTrackerAddon; } public get navigationMode(): INavigationMode | undefined { return this._navigationModeAddon; } - private readonly _onExit = new Emitter(); - public get onExit(): Event { return this._onExit.event; } + private readonly _onExit = new Emitter(); + public get onExit(): Event { return this._onExit.event; } private readonly _onDisposed = new Emitter(); public get onDisposed(): Event { return this._onDisposed.event; } private readonly _onFocused = new Emitter(); @@ -258,7 +259,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { public constructor( private readonly _terminalFocusContextKey: IContextKey, private readonly _configHelper: TerminalConfigHelper, - private _container: HTMLElement, + private _container: HTMLElement | undefined, private _shellLaunchConfig: IShellLaunchConfig, @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @@ -408,14 +409,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // The panel is minimized if (!this._isVisible) { return TerminalInstance._lastKnownCanvasDimensions; - } else { - // Trigger scroll event manually so that the viewport's scroll area is synced. This - // needs to happen otherwise its scrollTop value is invalid when the panel is toggled as - // it gets removed and then added back to the DOM (resetting scrollTop to 0). - // Upstream issue: https://github.com/sourcelair/xterm.js/issues/291 - if (this._xterm && this._xtermCore) { - this._xtermCore._onScroll.fire(this._xterm.buffer.viewportY); - } } if (!this._wrapperElement) { @@ -455,6 +448,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const Terminal = await this._getXtermConstructor(); const font = this._configHelper.getFont(undefined, true); const config = this._configHelper.config; + const fastScrollSensitivity = this._configurationService.getValue('editor.fastScrollSensitivity').fastScrollSensitivity; const xterm = new Terminal({ scrollback: config.scrollback, theme: this._getXtermTheme(), @@ -469,6 +463,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { macOptionIsMeta: config.macOptionIsMeta, macOptionClickForcesSelection: config.macOptionClickForcesSelection, rightClickSelectsWord: config.rightClickBehavior === 'selectWord', + fastScrollModifier: 'alt', + fastScrollSensitivity, // TODO: Guess whether to use canvas or dom better rendererType: config.rendererType === 'auto' ? 'canvas' : config.rendererType }); @@ -507,7 +503,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return false; }); } - this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, this._processManager, this._configHelper); + this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, xterm, this._processManager, this._configHelper); }); this._commandTrackerAddon = new CommandTrackerAddon(); @@ -548,7 +544,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } // The container changed, reattach - this._container.removeChild(this._wrapperElement); + if (this._container) { + this._container.removeChild(this._wrapperElement); + } this._container = container; this._container.appendChild(this._wrapperElement); } @@ -567,7 +565,14 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Attach the xterm object to the DOM, exposing it to the smoke tests this._wrapperElement.xterm = this._xterm; + this._wrapperElement.appendChild(this._xtermElement); + this._container.appendChild(this._wrapperElement); xterm.open(this._xtermElement); + + if (!xterm.element || !xterm.textarea) { + throw new Error('xterm elements not set after open'); + } + xterm.textarea.addEventListener('focus', () => this._onFocus.fire(this)); xterm.attachCustomKeyEventHandler((event: KeyboardEvent): boolean => { // Disable all input if the terminal is exiting @@ -579,7 +584,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // within commandsToSkipShell const standardKeyboardEvent = new StandardKeyboardEvent(event); const resolveResult = this._keybindingService.softDispatch(standardKeyboardEvent, standardKeyboardEvent.target); - if (resolveResult && this._skipTerminalCommands.some(k => k === resolveResult.commandId)) { + const allowChords = resolveResult && resolveResult.enterChord && this._configHelper.config.allowChords; + if (allowChords || resolveResult && this._skipTerminalCommands.some(k => k === resolveResult.commandId)) { event.preventDefault(); return false; } @@ -644,9 +650,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._refreshSelectionContextKey(); })); - this._wrapperElement.appendChild(this._xtermElement); - this._container.appendChild(this._wrapperElement); - const widgetManager = new TerminalWidgetManager(this._wrapperElement); this._widgetManager = widgetManager; this._processManager.onProcessReady(() => { @@ -801,7 +804,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (this._wrapperElement.xterm) { this._wrapperElement.xterm = undefined; } - if (this._wrapperElement.parentElement) { + if (this._wrapperElement.parentElement && this._container) { this._container.removeChild(this._wrapperElement); } } @@ -900,6 +903,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // necessary if the number of rows in the terminal has decreased while it was in the // background since scrollTop changes take no effect but the terminal's position does // change since the number of visible rows decreases. + // This can likely be removed after https://github.com/xtermjs/xterm.js/issues/291 is + // fixed upstream. this._xtermCore._onScroll.fire(this._xterm.buffer.viewportY); if (this._container && this._container.parentElement) { // Force a layout when the instance becomes invisible. This is particularly important @@ -1047,6 +1052,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidPathDirectory', 'The terminal shell path "{0}" is a directory', this._shellLaunchConfig.executable); } else if (exitCode === SHELL_CWD_INVALID_EXIT_CODE && this._shellLaunchConfig.cwd) { exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidCWD', 'The terminal shell CWD "{0}" does not exist', this._shellLaunchConfig.cwd.toString()); + } else if (exitCode === LEGACY_CONSOLE_MODE_EXIT_CODE) { + exitCodeMessage = nls.localize('terminal.integrated.legacyConsoleModeError', 'The terminal failed to launch properly because your system has legacy console mode enabled, uncheck "Use legacy console" cmd.exe\'s properties to fix this.'); } else if (this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) { let args = ''; if (typeof this._shellLaunchConfig.args === 'string') { @@ -1105,11 +1112,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } - this._onExit.fire(exitCode || 0); + this._onExit.fire(exitCode); } private _attachPressAnyKeyToCloseListener(xterm: XTermTerminal) { - if (!this._pressAnyKeyToCloseListener) { + if (xterm.textarea && !this._pressAnyKeyToCloseListener) { this._pressAnyKeyToCloseListener = dom.addDisposableListener(xterm.textarea, 'keypress', (event: KeyboardEvent) => { if (this._pressAnyKeyToCloseListener) { this._pressAnyKeyToCloseListener.dispose(); @@ -1240,6 +1247,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._safeSetOption('macOptionClickForcesSelection', config.macOptionClickForcesSelection); this._safeSetOption('rightClickSelectsWord', config.rightClickBehavior === 'selectWord'); this._safeSetOption('rendererType', config.rendererType === 'auto' ? 'canvas' : config.rendererType); + this._safeSetOption('fastScrollSensitivity', this._configurationService.getValue('editor.fastScrollSensitivity').fastScrollSensitivity); } public updateAccessibilitySupport(): void { @@ -1312,7 +1320,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return; } - if (this._xterm) { + if (this._xterm && this._xterm.element) { this._xterm.element.style.width = terminalWidth + 'px'; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts index f2040ec224..54bb231389 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts @@ -25,6 +25,7 @@ import { DataTransfers } from 'vs/base/browser/dnd'; import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { assertIsDefined } from 'vs/base/common/types'; const FIND_FOCUS_CLASS = 'find-focused'; @@ -70,7 +71,8 @@ export class TerminalPanel extends Panel { this._attachEventListeners(this._parentDomElement, this._terminalContainer); - this._terminalService.setContainers(this.getContainer(), this._terminalContainer); + const container = assertIsDefined(this.getContainer()); + this._terminalService.setContainers(container, this._terminalContainer); this._register(this.themeService.onThemeChange(theme => this._updateTheme(theme))); this._register(this._configurationService.onDidChangeConfiguration(e => { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 7d05daa9e1..0fdfe68bfa 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -11,13 +11,12 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { TerminalPanel } from 'vs/workbench/contrib/terminal/browser/terminalPanel'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { INotificationService } from 'vs/platform/notification/common/notification'; import { TerminalTab } from 'vs/workbench/contrib/terminal/browser/terminalTab'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance'; -import { IBrowserTerminalConfigHelper, ITerminalService, ITerminalInstance, ITerminalTab } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalService, ITerminalInstance, ITerminalTab } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { IQuickInputService, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput'; @@ -29,6 +28,7 @@ import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/termi import { isWindows, isMacintosh, OperatingSystem } from 'vs/base/common/platform'; import { basename } from 'vs/base/common/path'; import { IOpenFileRequest } from 'vs/platform/windows/common/windows'; +import { find } from 'vs/base/common/arrays'; interface IExtHostReadyEntry { promise: Promise; @@ -54,7 +54,7 @@ export class TerminalService implements ITerminalService { public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; } public get terminalTabs(): ITerminalTab[] { return this._terminalTabs; } - private _configHelper: IBrowserTerminalConfigHelper; + private _configHelper: TerminalConfigHelper; private _terminalContainer: HTMLElement | undefined; public get configHelper(): ITerminalConfigHelper { return this._configHelper; } @@ -91,7 +91,6 @@ export class TerminalService implements ITerminalService { @IPanelService private _panelService: IPanelService, @IWorkbenchLayoutService private _layoutService: IWorkbenchLayoutService, @ILifecycleService lifecycleService: ILifecycleService, - @INotificationService private _notificationService: INotificationService, @IDialogService private _dialogService: IDialogService, @IInstantiationService private _instantiationService: IInstantiationService, @IExtensionService private _extensionService: IExtensionService, @@ -392,12 +391,7 @@ export class TerminalService implements ITerminalService { return null; } - const instance = tab.split(this._terminalFocusContextKey, this.configHelper, shellLaunchConfig); - if (!instance) { - this._showNotEnoughSpaceToast(); - return null; - } - + const instance = tab.split(shellLaunchConfig); this._initInstanceListeners(instance); this._onInstancesChanged.fire(); @@ -414,13 +408,8 @@ export class TerminalService implements ITerminalService { instance.addDisposable(instance.onFocus(this._onActiveInstanceChanged.fire, this._onActiveInstanceChanged)); } - private _getTabForInstance(instance: ITerminalInstance): ITerminalTab | null { - for (const tab of this._terminalTabs) { - if (tab.terminalInstances.indexOf(instance) !== -1) { - return tab; - } - } - return null; + private _getTabForInstance(instance: ITerminalInstance): ITerminalTab | undefined { + return find(this._terminalTabs, tab => tab.terminalInstances.indexOf(instance) !== -1); } public showPanel(focus?: boolean): Promise { @@ -499,10 +488,6 @@ export class TerminalService implements ITerminalService { return !res.confirmed; } - protected _showNotEnoughSpaceToast(): void { - this._notificationService.info(nls.localize('terminal.minWidth', "Not enough space to split terminal.")); - } - protected _validateShellPaths(label: string, potentialPaths: string[]): Promise<[string, string] | null> { if (potentialPaths.length === 0) { return Promise.resolve(null); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTab.ts b/src/vs/workbench/contrib/terminal/browser/terminalTab.ts index cc1e0e6620..30c7c36014 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTab.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTab.ts @@ -14,7 +14,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ITerminalInstance, Direction, ITerminalTab, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; const SPLIT_PANE_MIN_SIZE = 120; -const TERMINAL_MIN_USEFUL_SIZE = 250; class SplitPaneContainer extends Disposable { private _height: number; @@ -370,18 +369,11 @@ export class TerminalTab extends Disposable implements ITerminalTab { this.terminalInstances.forEach(i => i.setVisible(visible)); } - public split( - terminalFocusContextKey: IContextKey, - configHelper: ITerminalConfigHelper, - shellLaunchConfig: IShellLaunchConfig - ): ITerminalInstance | undefined { + public split(shellLaunchConfig: IShellLaunchConfig): ITerminalInstance { if (!this._container) { throw new Error('Cannot split terminal that has not been attached'); } - const newTerminalSize = ((this._panelPosition === Position.BOTTOM ? this._container.clientWidth : this._container.clientHeight) / (this._terminalInstances.length + 1)); - if (newTerminalSize < TERMINAL_MIN_USEFUL_SIZE) { - return undefined; - } + const instance = this._terminalService.createInstance(undefined, shellLaunchConfig); this._terminalInstances.splice(this._activeInstanceIndex + 1, 0, instance); this._initInstanceListeners(instance); diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 6e7e734f3d..b2afc3e9fe 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -65,6 +65,7 @@ export const DEFAULT_LINE_HEIGHT = 1; export const SHELL_PATH_INVALID_EXIT_CODE = -1; export const SHELL_PATH_DIRECTORY_EXIT_CODE = -2; export const SHELL_CWD_INVALID_EXIT_CODE = -3; +export const LEGACY_CONSOLE_MODE_EXIT_CODE = 3221225786; // microsoft/vscode#73790 export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'; @@ -101,6 +102,7 @@ export interface ITerminalConfiguration { detectLocale: 'auto' | 'off' | 'on'; scrollback: number; commandsToSkipShell: string[]; + allowChords: boolean; cwd: string; confirmOnExit: boolean; enableBell: boolean; diff --git a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts index 0e23de9a2b..b18b8fd334 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts @@ -282,7 +282,6 @@ export function getDefaultShell( executable = configurationResolverService.resolve(lastActiveWorkspace, executable); } catch (e) { logService.error(`Could not resolve shell`, e); - executable = executable; } } diff --git a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts index 66445b15a4..3eae617061 100644 --- a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts +++ b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts @@ -237,7 +237,14 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess cols = Math.max(cols, 1); rows = Math.max(rows, 1); this._logService.trace('IPty#resize', cols, rows); - this._ptyProcess.resize(cols, rows); + try { + this._ptyProcess.resize(cols, rows); + } catch (e) { + // Swallow error if the pty has already exited + if (this._exitCode !== undefined) { + throw e; + } + } } } 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 df354f69d0..b4b833b311 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 @@ -20,10 +20,138 @@ function writePromise(term: Terminal, data: string): Promise { const ROWS = 10; const COLS = 10; -suite('Workbench - TerminalCommandTracker', () => { +suite.skip('Workbench - TerminalCommandTracker', () => { // {{SQL CARBON EDIT}} skip suite + let xterm: TestTerminal; + let commandTracker: CommandTrackerAddon; + + setup(async () => { + xterm = (new Terminal({ + cols: COLS, + rows: ROWS + })); + // Fill initial viewport + for (let i = 0; i < ROWS - 1; i++) { + await writePromise(xterm, `${i}\n`); + } + commandTracker = new CommandTrackerAddon(); + xterm.loadAddon(commandTracker); + }); + suite('Command tracking', () => { - test('should track commands when the prompt is of sufficient size', () => { - assert.equal(0, 0); + test('should track commands when the prompt is of sufficient size', async () => { + assert.equal(xterm.markers.length, 0); + await writePromise(xterm, '\x1b[3G'); // Move cursor to column 3 + xterm._core._onKey.fire({ key: '\x0d' }); + assert.equal(xterm.markers.length, 1); + }); + test('should not track commands when the prompt is too small', async () => { + assert.equal(xterm.markers.length, 0); + await writePromise(xterm, '\x1b[2G'); // Move cursor to column 2 + xterm._core._onKey.fire({ key: '\x0d' }); + assert.equal(xterm.markers.length, 0); + }); + }); + + suite('Commands', () => { + test('should scroll to the next and previous commands', async () => { + await writePromise(xterm, '\x1b[3G'); // Move cursor to column 3 + xterm._core._onKey.fire({ key: '\x0d' }); // Mark line #10 + assert.equal(xterm.markers[0].line, 9); + + for (let i = 0; i < 20; i++) { + await writePromise(xterm, `\r\n`); + } + assert.equal(xterm.buffer.baseY, 20); + assert.equal(xterm.buffer.viewportY, 20); + + // Scroll to marker + commandTracker.scrollToPreviousCommand(); + assert.equal(xterm.buffer.viewportY, 9); + + // Scroll to top boundary + commandTracker.scrollToPreviousCommand(); + assert.equal(xterm.buffer.viewportY, 0); + + // Scroll to marker + commandTracker.scrollToNextCommand(); + assert.equal(xterm.buffer.viewportY, 9); + + // Scroll to bottom boundary + commandTracker.scrollToNextCommand(); + assert.equal(xterm.buffer.viewportY, 20); + }); + test('should select to the next and previous commands', async () => { + (window).matchMedia = () => { + return { addListener: () => { } }; + }; + const e = document.createElement('div'); + document.body.appendChild(e); + xterm.open(e); + + await writePromise(xterm, '\r0'); + await writePromise(xterm, '\n\r1'); + await writePromise(xterm, '\x1b[3G'); // Move cursor to column 3 + xterm._core._onKey.fire({ key: '\x0d' }); // Mark line + assert.equal(xterm.markers[0].line, 10); + await writePromise(xterm, '\n\r2'); + await writePromise(xterm, '\x1b[3G'); // Move cursor to column 3 + xterm._core._onKey.fire({ key: '\x0d' }); // Mark line + assert.equal(xterm.markers[1].line, 11); + await writePromise(xterm, '\n\r3'); + + assert.equal(xterm.buffer.baseY, 3); + assert.equal(xterm.buffer.viewportY, 3); + + assert.equal(xterm.getSelection(), ''); + commandTracker.selectToPreviousCommand(); + assert.equal(xterm.getSelection(), '2'); + commandTracker.selectToPreviousCommand(); + assert.equal(xterm.getSelection(), isWindows ? '1\r\n2' : '1\n2'); + commandTracker.selectToNextCommand(); + assert.equal(xterm.getSelection(), '2'); + commandTracker.selectToNextCommand(); + assert.equal(xterm.getSelection(), isWindows ? '\r\n' : '\n'); + + document.body.removeChild(e); + }); + test('should select to the next and previous lines & commands', async () => { + (window).matchMedia = () => { + return { addListener: () => { } }; + }; + const e = document.createElement('div'); + document.body.appendChild(e); + xterm.open(e); + + await writePromise(xterm, '\r0'); + await writePromise(xterm, '\n\r1'); + await writePromise(xterm, '\x1b[3G'); // Move cursor to column 3 + xterm._core._onKey.fire({ key: '\x0d' }); // Mark line + assert.equal(xterm.markers[0].line, 10); + await writePromise(xterm, '\n\r2'); + await writePromise(xterm, '\x1b[3G'); // Move cursor to column 3 + xterm._core._onKey.fire({ key: '\x0d' }); // Mark line + assert.equal(xterm.markers[1].line, 11); + await writePromise(xterm, '\n\r3'); + + assert.equal(xterm.buffer.baseY, 3); + assert.equal(xterm.buffer.viewportY, 3); + + assert.equal(xterm.getSelection(), ''); + commandTracker.selectToPreviousLine(); + assert.equal(xterm.getSelection(), '2'); + commandTracker.selectToNextLine(); + commandTracker.selectToNextLine(); + assert.equal(xterm.getSelection(), '3'); + commandTracker.selectToPreviousCommand(); + commandTracker.selectToPreviousCommand(); + commandTracker.selectToNextLine(); + assert.equal(xterm.getSelection(), '2'); + commandTracker.selectToPreviousCommand(); + assert.equal(xterm.getSelection(), isWindows ? '1\r\n2' : '1\n2'); + commandTracker.selectToPreviousLine(); + assert.equal(xterm.getSelection(), isWindows ? '0\r\n1\r\n2' : '0\n1\n2'); + + document.body.removeChild(e); }); }); }); diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index 0228260f75..2b10c0a23a 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -14,7 +14,6 @@ import { IWorkbenchThemeService, COLOR_THEME_SETTING, ICON_THEME_SETTING, IColor import { VIEWLET_ID, IExtensionsViewlet } from 'vs/workbench/contrib/extensions/common/extensions'; import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { Delayer } from 'vs/base/common/async'; import { IColorRegistry, Extensions as ColorRegistryExtensions } from 'vs/platform/theme/common/colorRegistry'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Color } from 'vs/base/common/color'; @@ -27,7 +26,7 @@ import { IQuickInputService, QuickPickInput } from 'vs/platform/quickinput/commo export class SelectColorThemeAction extends Action { static readonly ID = 'workbench.action.selectTheme'; - static LABEL = localize('selectTheme.label', "Color Theme"); + static readonly LABEL = localize('selectTheme.label', "Color Theme"); constructor( id: string, @@ -53,34 +52,43 @@ export class SelectColorThemeAction extends Action { // ...configurationEntries(this.extensionGalleryService, localize('installColorThemes', "Install Additional Color Themes...")) ]; - const selectTheme = (theme: ThemeItem, applyTheme: boolean) => { - let themeId = theme.id; - if (typeof theme.id === 'undefined') { // 'pick in marketplace' entry - if (applyTheme) { - openExtensionViewlet(this.viewletService, 'category:themes '); - } - themeId = currentTheme.id; - } - let target: ConfigurationTarget | undefined = undefined; - if (applyTheme) { - let confValue = this.configurationService.inspect(COLOR_THEME_SETTING); - target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; - } + let selectThemeTimeout: number | undefined; - this.themeService.setColorTheme(themeId, target).then(undefined, - err => { - onUnexpectedError(err); - this.themeService.setColorTheme(currentTheme.id, undefined); + const selectTheme = (theme: ThemeItem, applyTheme: boolean) => { + if (selectThemeTimeout) { + clearTimeout(selectThemeTimeout); + } + selectThemeTimeout = window.setTimeout(() => { + selectThemeTimeout = undefined; + + let themeId = theme.id; + if (typeof theme.id === 'undefined') { // 'pick in marketplace' entry + if (applyTheme) { + openExtensionViewlet(this.viewletService, 'category:themes '); + } + themeId = currentTheme.id; } - ); + let target: ConfigurationTarget | undefined = undefined; + if (applyTheme) { + let confValue = this.configurationService.inspect(COLOR_THEME_SETTING); + target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; + } + + this.themeService.setColorTheme(themeId, target).then(undefined, + err => { + onUnexpectedError(err); + this.themeService.setColorTheme(currentTheme.id, undefined); + } + ); + }, applyTheme ? 0 : 200); }; const placeHolder = localize('themes.selectTheme', "Select Color Theme (Up/Down Keys to Preview)"); const autoFocusIndex = firstIndex(picks, p => isItem(p) && p.id === currentTheme.id); const activeItem: ThemeItem = picks[autoFocusIndex] as ThemeItem; - const delayer = new Delayer(100); - const chooseTheme = (theme: ThemeItem) => delayer.trigger(() => selectTheme(theme || currentTheme, true), 0); - const tryTheme = (theme: ThemeItem) => delayer.trigger(() => selectTheme(theme, false)); + + const chooseTheme = (theme: ThemeItem) => selectTheme(theme || currentTheme, true); + const tryTheme = (theme: ThemeItem) => selectTheme(theme, false); return this.quickInputService.pick(picks, { placeHolder, activeItem, onDidFocus: tryTheme }) .then(chooseTheme); @@ -91,7 +99,7 @@ export class SelectColorThemeAction extends Action { class SelectIconThemeAction extends Action { static readonly ID = 'workbench.action.selectIconTheme'; - static LABEL = localize('selectIconTheme.label', "File Icon Theme"); + static readonly LABEL = localize('selectIconTheme.label', "File Icon Theme"); constructor( id: string, @@ -117,33 +125,40 @@ class SelectIconThemeAction extends Action { // configurationEntries(this.extensionGalleryService, localize('installIconThemes', "Install Additional File Icon Themes...")) ); + let selectThemeTimeout: number | undefined; + const selectTheme = (theme: ThemeItem, applyTheme: boolean) => { - let themeId = theme.id; - if (typeof theme.id === 'undefined') { // 'pick in marketplace' entry + if (selectThemeTimeout) { + clearTimeout(selectThemeTimeout); + } + selectThemeTimeout = window.setTimeout(() => { + selectThemeTimeout = undefined; + let themeId = theme.id; + if (typeof theme.id === 'undefined') { // 'pick in marketplace' entry + if (applyTheme) { + openExtensionViewlet(this.viewletService, 'tag:icon-theme '); + } + themeId = currentTheme.id; + } + let target: ConfigurationTarget | undefined = undefined; if (applyTheme) { - openExtensionViewlet(this.viewletService, 'tag:icon-theme '); + let confValue = this.configurationService.inspect(ICON_THEME_SETTING); + target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; } - themeId = currentTheme.id; - } - let target: ConfigurationTarget | undefined = undefined; - if (applyTheme) { - let confValue = this.configurationService.inspect(ICON_THEME_SETTING); - target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; - } - this.themeService.setFileIconTheme(themeId, target).then(undefined, - err => { - onUnexpectedError(err); - this.themeService.setFileIconTheme(currentTheme.id, undefined); - } - ); + this.themeService.setFileIconTheme(themeId, target).then(undefined, + err => { + onUnexpectedError(err); + this.themeService.setFileIconTheme(currentTheme.id, undefined); + } + ); + }, applyTheme ? 0 : 200); }; const placeHolder = localize('themes.selectIconTheme', "Select File Icon Theme"); const autoFocusIndex = firstIndex(picks, p => isItem(p) && p.id === currentTheme.id); const activeItem: ThemeItem = picks[autoFocusIndex] as ThemeItem; - const delayer = new Delayer(100); - const chooseTheme = (theme: ThemeItem) => delayer.trigger(() => selectTheme(theme || currentTheme, true), 0); - const tryTheme = (theme: ThemeItem) => delayer.trigger(() => selectTheme(theme, false)); + const chooseTheme = (theme: ThemeItem) => selectTheme(theme || currentTheme, true); + const tryTheme = (theme: ThemeItem) => selectTheme(theme, false); return this.quickInputService.pick(picks, { placeHolder, activeItem, onDidFocus: tryTheme }) .then(chooseTheme); @@ -199,7 +214,7 @@ function toEntries(themes: Array, label?: string): class GenerateColorThemeAction extends Action { static readonly ID = 'workbench.action.generateColorTheme'; - static LABEL = localize('generateColorTheme.label', "Generate Color Theme From Current Settings"); + static readonly LABEL = localize('generateColorTheme.label', "Generate Color Theme From Current Settings"); constructor( id: string, @@ -290,4 +305,4 @@ MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { title: localize('themes.selectIconTheme.label', "File Icon Theme") }, order: 2 -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index 802d2d9a76..2b7ab73aa4 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -17,7 +17,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IRequestService, asText } from 'vs/platform/request/common/request'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IWebviewEditorService } from 'vs/workbench/contrib/webview/browser/webviewEditorService'; +import { IWebviewWorkbenchService } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; @@ -43,7 +43,7 @@ export class ReleaseNotesManager { @ITelemetryService private readonly _telemetryService: ITelemetryService, @IEditorService private readonly _editorService: IEditorService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, - @IWebviewEditorService private readonly _webviewEditorService: IWebviewEditorService, + @IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService, @IExtensionService private readonly _extensionService: IExtensionService, @IProductService private readonly _productService: IProductService ) { @@ -71,9 +71,9 @@ export class ReleaseNotesManager { if (this._currentReleaseNotes) { this._currentReleaseNotes.setName(title); this._currentReleaseNotes.webview.html = html; - this._webviewEditorService.revealWebview(this._currentReleaseNotes, activeControl ? activeControl.group : this._editorGroupService.activeGroup, false); + this._webviewWorkbenchService.revealWebview(this._currentReleaseNotes, activeControl ? activeControl.group : this._editorGroupService.activeGroup, false); } else { - this._currentReleaseNotes = this._webviewEditorService.createWebview( + this._currentReleaseNotes = this._webviewWorkbenchService.createWebview( generateUuid(), 'releaseNotes', title, diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index efaccdcf2f..9a1ac92a44 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -112,7 +112,7 @@ export class ShowReleaseNotesAction extends AbstractShowReleaseNotesAction { export class ShowCurrentReleaseNotesAction extends AbstractShowReleaseNotesAction { static readonly ID = ShowCurrentReleaseNotesActionId; - static LABEL = nls.localize('showReleaseNotes', "Show Release Notes"); + static readonly LABEL = nls.localize('showReleaseNotes', "Show Release Notes"); constructor( id = ShowCurrentReleaseNotesAction.ID, @@ -182,7 +182,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu private readonly badgeDisposable = this._register(new MutableDisposable()); private updateStateContextKey: IContextKey; - private windowId: number | undefined = this.electronEnvironmentService ? this.electronEnvironmentService.windowId : undefined; + private context = `window:${this.electronEnvironmentService ? this.electronEnvironmentService.windowId : 'any'}`; constructor( @IStorageService private readonly storageService: IStorageService, @@ -229,7 +229,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu case StateType.Idle: if (state.error) { this.onError(state.error); - } else if (this.state.type === StateType.CheckingForUpdates && this.state.context && this.state.context.windowId === this.windowId) { + } else if (this.state.type === StateType.CheckingForUpdates && this.state.context === this.context) { this.onUpdateNotAvailable(); } break; @@ -412,7 +412,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu } private registerGlobalActivityActions(): void { - CommandsRegistry.registerCommand('update.check', () => this.updateService.checkForUpdates({ windowId: this.windowId })); + CommandsRegistry.registerCommand('update.check', () => this.updateService.checkForUpdates(this.context)); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '6_update', command: { @@ -480,7 +480,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu group: '6_update', command: { id: 'update.restart', - title: nls.localize('restartToUpdate', "Restart to Update") + title: nls.localize('restartToUpdate', "Restart to Update (1)") }, when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Ready) }); diff --git a/src/vs/workbench/contrib/update/electron-browser/update.contribution.ts b/src/vs/workbench/contrib/update/electron-browser/update.contribution.ts deleted file mode 100644 index 8a22f10e7a..0000000000 --- a/src/vs/workbench/contrib/update/electron-browser/update.contribution.ts +++ /dev/null @@ -1,18 +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 platform from 'vs/base/common/platform'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { Win3264BitContribution } from 'vs/workbench/contrib/update/electron-browser/update'; -import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; - -const workbench = Registry.as(WorkbenchExtensions.Workbench); - -if (platform.isWindows) { - if (process.arch === 'ia32') { - workbench.registerWorkbenchContribution(Win3264BitContribution, LifecyclePhase.Restored); - } -} diff --git a/src/vs/workbench/contrib/update/electron-browser/update.ts b/src/vs/workbench/contrib/update/electron-browser/update.ts deleted file mode 100644 index 4b461e2fd6..0000000000 --- a/src/vs/workbench/contrib/update/electron-browser/update.ts +++ /dev/null @@ -1,40 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import severity from 'vs/base/common/severity'; -import product from 'vs/platform/product/common/product'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INotificationService } from 'vs/platform/notification/common/notification'; - -export class Win3264BitContribution implements IWorkbenchContribution { - - private static readonly URL = 'https://code.visualstudio.com/updates/v1_15#_windows-64-bit'; - private static readonly INSIDER_URL = 'https://github.com/Microsoft/vscode-docs/blob/vnext/release-notes/v1_15.md#windows-64-bit'; - - constructor( - @INotificationService notificationService: INotificationService, - @IEnvironmentService environmentService: IEnvironmentService - ) { - if (environmentService.disableUpdates) { - return; - } - - const url = product.quality === 'insider' - ? Win3264BitContribution.INSIDER_URL - : Win3264BitContribution.URL; - - notificationService.prompt( - severity.Info, - nls.localize('64bitisavailable', "{0} for 64-bit Windows is now available! Click [here]({1}) to learn more.", product.nameShort, url), - [], - { - sticky: true, - neverShowAgain: { id: 'neverShowAgain:update/win32-64bits', isSecondary: true } - } - ); - } -} diff --git a/src/vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider.ts b/src/vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider.ts index 57e3928061..56e39a6137 100644 --- a/src/vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider.ts +++ b/src/vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider.ts @@ -33,8 +33,7 @@ const TRUSTED_DOMAINS_STAT: IStat = { size: 0 }; -const CONFIG_HELP_TEXT_PRE = - `// Links matching one or more entries in the list below can be opened without link protection. +const CONFIG_HELP_TEXT_PRE = `// Links matching one or more entries in the list below can be opened without link protection. // The following examples show what entries can look like: // - "https://microsoft.com": Matches this specific domain using https // - "https://*.microsoft.com": Match all domains ending in "microsoft.com" using https @@ -95,23 +94,36 @@ export class TrustedDomainsFileSystemProvider implements IFileSystemProvider, IW } readFile(resource: URI): Promise { - const { defaultTrustedDomains, trustedDomains } = readTrustedDomains(this.storageService, this.productService); + let trustedDomainsContent = this.storageService.get( + 'http.linkProtectionTrustedDomainsContent', + StorageScope.GLOBAL + ); + + if ( + !trustedDomainsContent || + trustedDomainsContent.indexOf(CONFIG_HELP_TEXT_PRE) === -1 || + trustedDomainsContent.indexOf(CONFIG_HELP_TEXT_AFTER) === -1 + ) { + const { defaultTrustedDomains, trustedDomains } = readTrustedDomains(this.storageService, this.productService); + + trustedDomainsContent = computeTrustedDomainContent(defaultTrustedDomains, trustedDomains); + } - const trustedDomainsContent = computeTrustedDomainContent(defaultTrustedDomains, trustedDomains); const buffer = VSBuffer.fromString(trustedDomainsContent).buffer; return Promise.resolve(buffer); } writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { try { - const trustedDomains = parse(content.toString()); + const trustedDomainsContent = content.toString(); + const trustedDomains = parse(trustedDomainsContent); + this.storageService.store('http.linkProtectionTrustedDomainsContent', trustedDomainsContent, StorageScope.GLOBAL); this.storageService.store( 'http.linkProtectionTrustedDomains', JSON.stringify(trustedDomains), StorageScope.GLOBAL ); - } catch (err) { } return Promise.resolve(); diff --git a/src/vs/workbench/contrib/url/common/url.contribution.ts b/src/vs/workbench/contrib/url/common/url.contribution.ts index 546e032f8d..550feb0395 100644 --- a/src/vs/workbench/contrib/url/common/url.contribution.ts +++ b/src/vs/workbench/contrib/url/common/url.contribution.ts @@ -35,7 +35,7 @@ export class OpenUrlAction extends Action { run(): Promise { return this.quickInputService.input({ prompt: 'URL to open' }).then(input => { const uri = URI.parse(input); - this.urlService.open(uri); + this.urlService.open(uri, { trusted: true }); }); } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index d4f130ea57..0a77ca5b0e 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -116,7 +116,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } else if (this.userDataSyncService.status === SyncStatus.HasConflicts) { badge = new NumberBadge(1, () => localize('resolve conflicts', "Resolve Conflicts")); } else if (this.userDataSyncService.status === SyncStatus.Syncing) { - badge = new ProgressBadge(() => localize('syncing', "Synchronising User Configuration...")); + badge = new ProgressBadge(() => localize('syncing', "Synchronizing User Configuration...")); clazz = 'progress-badge'; } diff --git a/src/vs/workbench/contrib/watermark/browser/watermark.ts b/src/vs/workbench/contrib/watermark/browser/watermark.ts index b283c86d16..a8880c6547 100644 --- a/src/vs/workbench/contrib/watermark/browser/watermark.ts +++ b/src/vs/workbench/contrib/watermark/browser/watermark.ts @@ -28,6 +28,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IDimension } from 'vs/platform/layout/browser/layoutService'; import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal'; +import { assertIsDefined } from 'vs/base/common/types'; // {{SQL CARBON EDIT}} import { NewNotebookAction } from 'sql/workbench/parts/notebook/browser/notebookActions'; @@ -130,7 +131,7 @@ export class WatermarkContribution extends Disposable implements IWorkbenchContr } private create(): void { - const container = this.layoutService.getContainer(Parts.EDITOR_PART); + const container = assertIsDefined(this.layoutService.getContainer(Parts.EDITOR_PART)); container.classList.add('has-watermark'); this.watermark = $('.watermark'); @@ -176,7 +177,9 @@ export class WatermarkContribution extends Disposable implements IWorkbenchContr this.watermark.remove(); const container = this.layoutService.getContainer(Parts.EDITOR_PART); - container.classList.remove('has-watermark'); + if (container) { + container.classList.remove('has-watermark'); + } this.watermarkDisposable.clear(); } diff --git a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts new file mode 100644 index 0000000000..45e296b7a1 --- /dev/null +++ b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts @@ -0,0 +1,290 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { addClass } from 'vs/base/browser/dom'; +import { Emitter } from 'vs/base/common/event'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { WebviewExtensionDescription, WebviewOptions, WebviewContentOptions } from 'vs/workbench/contrib/webview/browser/webview'; +import { URI } from 'vs/base/common/uri'; +import { areWebviewInputOptionsEqual } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; +import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/common/themeing'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; + +export const enum WebviewMessageChannels { + onmessage = 'onmessage', + didClickLink = 'did-click-link', + didScroll = 'did-scroll', + didFocus = 'did-focus', + didBlur = 'did-blur', + doUpdateState = 'do-update-state', + doReload = 'do-reload', + loadResource = 'load-resource', + loadLocalhost = 'load-localhost', + webviewReady = 'webview-ready', +} + +interface IKeydownEvent { + key: string; + keyCode: number; + code: string; + shiftKey: boolean; + altKey: boolean; + ctrlKey: boolean; + metaKey: boolean; + repeat: boolean; +} + +interface WebviewContent { + readonly html: string; + readonly options: WebviewContentOptions; + readonly state: string | undefined; +} + +export abstract class BaseWebview extends Disposable { + + private _element: T | undefined; + protected get element(): T | undefined { return this._element; } + + private _focused: boolean; + protected get focused(): boolean { return this._focused; } + + private readonly _ready: Promise; + + protected content: WebviewContent; + + public extension: WebviewExtensionDescription | undefined; + + constructor( + // TODO: matb, this should not be protected. The only reason it needs to be is that the base class ends up using it in the call to createElement + protected readonly id: string, + options: WebviewOptions, + contentOptions: WebviewContentOptions, + private readonly webviewThemeDataProvider: WebviewThemeDataProvider, + @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IEnvironmentService private readonly _environementService: IEnvironmentService, + @IWorkbenchEnvironmentService protected readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, + ) { + super(); + + this.content = { + html: '', + options: contentOptions, + state: undefined + }; + + this._element = this.createElement(options); + + this._ready = new Promise(resolve => { + const subscription = this._register(this.on(WebviewMessageChannels.webviewReady, () => { + if (this.element) { + addClass(this.element, 'ready'); + } + subscription.dispose(); + resolve(); + })); + }); + + this._register(this.on('no-csp-found', () => { + this.handleNoCspFound(); + })); + + this._register(this.on(WebviewMessageChannels.didClickLink, (uri: string) => { + this._onDidClickLink.fire(URI.parse(uri)); + })); + + this._register(this.on(WebviewMessageChannels.onmessage, (data: any) => { + this._onMessage.fire(data); + })); + + this._register(this.on(WebviewMessageChannels.didScroll, (scrollYPercentage: number) => { + this._onDidScroll.fire({ scrollYPercentage: scrollYPercentage }); + })); + + this._register(this.on(WebviewMessageChannels.doReload, () => { + this.reload(); + })); + + this._register(this.on(WebviewMessageChannels.doUpdateState, (state: any) => { + this.state = state; + this._onDidUpdateState.fire(state); + })); + + this._register(this.on(WebviewMessageChannels.didFocus, () => { + this.handleFocusChange(true); + })); + + this._register(this.on(WebviewMessageChannels.didBlur, () => { + this.handleFocusChange(false); + })); + + this._register(this.on('did-keydown', (data: KeyboardEvent) => { + // Electron: workaround for https://github.com/electron/electron/issues/14258 + // We have to detect keyboard events in the and dispatch them to our + // keybinding service because these events do not bubble to the parent window anymore. + this.handleKeyDown(data); + })); + + this.style(); + this._register(webviewThemeDataProvider.onThemeDataChanged(this.style, this)); + } + + dispose(): void { + if (this.element) { + this.element.remove(); + } + + this._element = undefined; + super.dispose(); + } + + private readonly _onMissingCsp = this._register(new Emitter()); + public readonly onMissingCsp = this._onMissingCsp.event; + + private readonly _onDidClickLink = this._register(new Emitter()); + public readonly onDidClickLink = this._onDidClickLink.event; + + private readonly _onMessage = this._register(new Emitter()); + public readonly onMessage = this._onMessage.event; + + private readonly _onDidScroll = this._register(new Emitter<{ readonly scrollYPercentage: number; }>()); + public readonly onDidScroll = this._onDidScroll.event; + + private readonly _onDidUpdateState = this._register(new Emitter()); + public readonly onDidUpdateState = this._onDidUpdateState.event; + + private readonly _onDidFocus = this._register(new Emitter()); + public readonly onDidFocus = this._onDidFocus.event; + + public sendMessage(data: any): void { + this._send('message', data); + } + + protected _send(channel: string, data?: any): void { + this._ready + .then(() => this.postMessage(channel, data)) + .catch(err => console.error(err)); + } + + protected abstract readonly extraContentOptions: { readonly [key: string]: string }; + + protected abstract createElement(options: WebviewOptions): T; + + protected abstract on(channel: string, handler: (data: T) => void): IDisposable; + + protected abstract postMessage(channel: string, data?: any): void; + + private _hasAlertedAboutMissingCsp = false; + private handleNoCspFound(): void { + if (this._hasAlertedAboutMissingCsp) { + return; + } + this._hasAlertedAboutMissingCsp = true; + + if (this.extension && this.extension.id) { + if (this._environementService.isExtensionDevelopment) { + this._onMissingCsp.fire(this.extension.id); + } + + type TelemetryClassification = { + extension?: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; }; + }; + type TelemetryData = { + extension?: string, + }; + + this._telemetryService.publicLog2('webviewMissingCsp', { + extension: this.extension.id.value + }); + } + } + + public reload(): void { + this.doUpdateContent(); + } + + public set html(value: string) { + this.content = { + html: value, + options: this.content.options, + state: this.content.state, + }; + this.doUpdateContent(); + } + + public set contentOptions(options: WebviewContentOptions) { + if (areWebviewInputOptionsEqual(options, this.content.options)) { + return; + } + + this.content = { + html: this.content.html, + options: options, + state: this.content.state, + }; + this.doUpdateContent(); + } + + public set state(state: string | undefined) { + this.content = { + html: this.content.html, + options: this.content.options, + state, + }; + } + + public set initialScrollProgress(value: number) { + this._send('initial-scroll-position', value); + } + + private doUpdateContent() { + this._send('content', { + contents: this.content.html, + options: this.content.options, + state: this.content.state, + ...this.extraContentOptions + }); + } + + protected style(): void { + const { styles, activeTheme } = this.webviewThemeDataProvider.getWebviewThemeData(); + this._send('styles', { styles, activeTheme }); + } + + protected handleFocusChange(isFocused: boolean): void { + this._focused = isFocused; + if (isFocused) { + this._onDidFocus.fire(); + } + } + + private handleKeyDown(event: IKeydownEvent) { + // Create a fake KeyboardEvent from the data provided + const emulatedKeyboardEvent = new KeyboardEvent('keydown', event); + // Force override the target + Object.defineProperty(emulatedKeyboardEvent, 'target', { + get: () => this.element, + }); + // And re-dispatch + window.dispatchEvent(emulatedKeyboardEvent); + } + + windowDidDragStart(): void { + // Webview break drag and droping around the main window (no events are generated when you are over them) + // Work around this by disabling pointer events during the drag. + // https://github.com/electron/electron/issues/18226 + if (this.element) { + this.element.style.pointerEvents = 'none'; + } + } + + windowDidDragEnd(): void { + if (this.element) { + this.element.style.pointerEvents = ''; + } + } +} diff --git a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts index 5da5747d0c..260b800e13 100644 --- a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts +++ b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts @@ -8,8 +8,8 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { IWebviewService, Webview, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; -import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; +import { IWebviewService, Webview, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions, WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { Dimension } from 'vs/base/browser/dom'; /** @@ -24,22 +24,25 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd private _html: string = ''; private _initialScrollProgress: number = 0; private _state: string | undefined = undefined; - private _extension: { - readonly location: URI; - readonly id?: ExtensionIdentifier; - } | undefined; + private _extension: WebviewExtensionDescription | undefined; + + private _contentOptions: WebviewContentOptions; + private _options: WebviewOptions; private _owner: any = undefined; public constructor( private readonly id: string, - public readonly options: WebviewOptions, - private _contentOptions: WebviewContentOptions, + initialOptions: WebviewOptions, + initialContentOptions: WebviewContentOptions, @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, @IWebviewService private readonly _webviewService: IWebviewService ) { super(); + this._options = initialOptions; + this._contentOptions = initialContentOptions; + this._register(toDisposable(() => this.container.remove())); } @@ -47,7 +50,12 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd public get container() { const container = document.createElement('div'); container.id = `webview-${this.id}`; - this._layoutService.getContainer(Parts.EDITOR_PART).appendChild(container); + container.style.visibility = 'hidden'; + + // Webviews cannot be reparented in the dom as it will destory their contents. + // Mount them to a high level node to avoid this. + this._layoutService.getWorkbenchElement().appendChild(container); + return container; } @@ -62,7 +70,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd } this._owner = undefined; this.container.style.visibility = 'hidden'; - if (!this.options.retainContextWhenHidden) { + if (!this._options.retainContextWhenHidden) { this._webview.clear(); this._webviewEvents.clear(); } @@ -83,31 +91,32 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd private show() { if (!this._webview.value) { - const webview = this._webviewService.createWebview(this.id, this.options, this._contentOptions); + const webview = this._webviewService.createWebview(this.id, this._options, this._contentOptions); this._webview.value = webview; webview.state = this._state; webview.html = this._html; webview.extension = this._extension; - if (this.options.tryRestoreScrollPosition) { + if (this._options.tryRestoreScrollPosition) { webview.initialScrollProgress = this._initialScrollProgress; } this._webview.value.mountTo(this.container); + + // Forward events from inner webview to outer listeners this._webviewEvents.clear(); + this._webviewEvents.add(webview.onDidFocus(() => { this._onDidFocus.fire(); })); + this._webviewEvents.add(webview.onDidClickLink(x => { this._onDidClickLink.fire(x); })); + this._webviewEvents.add(webview.onMessage(x => { this._onMessage.fire(x); })); + this._webviewEvents.add(webview.onMissingCsp(x => { this._onMissingCsp.fire(x); })); - webview.onDidFocus(() => { this._onDidFocus.fire(); }, undefined, this._webviewEvents); - webview.onDidClickLink(x => { this._onDidClickLink.fire(x); }, undefined, this._webviewEvents); - webview.onMessage(x => { this._onMessage.fire(x); }, undefined, this._webviewEvents); - webview.onMissingCsp(x => { this._onMissingCsp.fire(x); }, undefined, this._webviewEvents); - - webview.onDidScroll(x => { + this._webviewEvents.add(webview.onDidScroll(x => { this._initialScrollProgress = x.scrollYPercentage; this._onDidScroll.fire(x); - }, undefined, this._webviewEvents); + })); - webview.onDidUpdateState(state => { + this._webviewEvents.add(webview.onDidUpdateState(state => { this._state = state; this._onDidUpdateState.fire(state); - }, undefined, this._webviewEvents); + })); this._pendingMessages.forEach(msg => webview.sendMessage(msg)); this._pendingMessages.clear(); @@ -133,6 +142,9 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd this.withWebview(webview => webview.state = value); } + public get options(): WebviewOptions { return this._options; } + public set options(value: WebviewOptions) { this._options = value; } + public get contentOptions(): WebviewContentOptions { return this._contentOptions; } public set contentOptions(value: WebviewContentOptions) { this._contentOptions = value; @@ -171,15 +183,6 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd } } - update(html: string, options: WebviewContentOptions, retainContextWhenHidden: boolean): void { - this._contentOptions = options; - this._html = html; - this.withWebview(webview => { - webview.update(html, options, retainContextWhenHidden); - }); - } - - layout(): void { this.withWebview(webview => webview.layout()); } focus(): void { this.withWebview(webview => webview.focus()); } reload(): void { this.withWebview(webview => webview.reload()); } showFind(): void { this.withWebview(webview => webview.showFind()); } @@ -195,4 +198,12 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd f(this._webview.value); } } + + windowDidDragStart() { + this.withWebview(webview => webview.windowDidDragStart()); + } + + windowDidDragEnd() { + this.withWebview(webview => webview.windowDidDragEnd()); + } } diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index b4cd2b97b6..e2dad2524f 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -409,8 +409,6 @@ newFrame.contentDocument.open(); } - newFrame.contentWindow.addEventListener('keydown', handleInnerKeydown); - newFrame.contentWindow.addEventListener('DOMContentLoaded', e => { // Workaround for https://bugs.chromium.org/p/chromium/issues/detail?id=978325 setTimeout(() => { @@ -477,9 +475,10 @@ } }); - // Bubble out link clicks + // Bubble out various events newFrame.contentWindow.addEventListener('click', handleInnerClick); newFrame.contentWindow.addEventListener('auxclick', handleAuxClick); + newFrame.contentWindow.addEventListener('keydown', handleInnerKeydown); if (host.onIframeLoaded) { host.onIframeLoaded(newFrame); diff --git a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts index 8640bd7e24..e1e810fbe5 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts @@ -19,7 +19,7 @@ import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, webviewDeveloperCategor import { HideWebViewEditorFindCommand, ReloadWebviewAction, ShowWebViewEditorFindWidgetCommand, WebViewEditorFindNextCommand, WebViewEditorFindPreviousCommand } from '../browser/webviewCommands'; import { WebviewEditor } from '../browser/webviewEditor'; import { WebviewInput } from '../browser/webviewEditorInput'; -import { IWebviewEditorService, WebviewEditorService } from '../browser/webviewEditorService'; +import { IWebviewWorkbenchService, WebviewEditorService } from './webviewWorkbenchService'; (Registry.as(EditorExtensions.Editors)).registerEditor(new EditorDescriptor( WebviewEditor, @@ -31,7 +31,7 @@ Registry.as(EditorInputExtensions.EditorInputFactor WebviewEditorInputFactory.ID, WebviewEditorInputFactory); -registerSingleton(IWebviewEditorService, WebviewEditorService, true); +registerSingleton(IWebviewWorkbenchService, WebviewEditorService, true); const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index 15f61d4043..10db2b70ac 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -54,14 +54,16 @@ export interface WebviewContentOptions { readonly enableCommandUris?: boolean; } +export interface WebviewExtensionDescription { + readonly location: URI; + readonly id: ExtensionIdentifier; +} + export interface Webview extends IDisposable { html: string; contentOptions: WebviewContentOptions; - extension: { - readonly location: URI; - readonly id?: ExtensionIdentifier; - } | undefined; + extension: WebviewExtensionDescription | undefined; initialScrollProgress: number; state: string | undefined; @@ -73,19 +75,16 @@ export interface Webview extends IDisposable { readonly onMissingCsp: Event; sendMessage(data: any): void; - update( - html: string, - options: WebviewContentOptions, - retainContextWhenHidden: boolean - ): void; - layout(): void; focus(): void; reload(): void; showFind(): void; hideFind(): void; runFindAction(previous: boolean): void; + + windowDidDragStart(): void; + windowDidDragEnd(): void; } export interface WebviewElement extends Webview { @@ -94,7 +93,7 @@ export interface WebviewElement extends Webview { export interface WebviewEditorOverlay extends Webview { readonly container: HTMLElement; - readonly options: WebviewOptions; + options: WebviewOptions; claim(owner: any): void; release(owner: any): void; diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts index d958363ced..2a027e02a9 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts @@ -6,22 +6,24 @@ import * as DOM from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { isWeb } from 'vs/base/common/platform'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { EditorOptions, EditorInput } from 'vs/workbench/common/editor'; -import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; +import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; +import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, Webview, WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; -import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; +import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export class WebviewEditor extends BaseEditor { - public static ID = 'WebviewEditor'; + public static readonly ID = 'WebviewEditor'; private readonly _scopedContextKeyService = this._register(new MutableDisposable()); private _findWidgetVisible: IContextKey; @@ -29,7 +31,7 @@ export class WebviewEditor extends BaseEditor { private _content?: HTMLElement; private _dimension?: DOM.Dimension; - private readonly _webviewFocusTrackerDisposables = this._register(new DisposableStore()); + private readonly _webviewVisibleDisposables = this._register(new DisposableStore()); private readonly _onFocusWindowHandler = this._register(new MutableDisposable()); private readonly _onDidFocusWebview = this._register(new Emitter()); @@ -38,10 +40,11 @@ export class WebviewEditor extends BaseEditor { constructor( @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, + @IStorageService storageService: IStorageService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IEditorService private readonly _editorService: IEditorService, + @IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService, @IHostService private readonly _hostService: IHostService, - @IStorageService storageService: IStorageService ) { super(WebviewEditor.ID, telemetryService, themeService, storageService); @@ -93,13 +96,12 @@ export class WebviewEditor extends BaseEditor { this._dimension = dimension; if (this.input && this.input instanceof WebviewInput) { this.synchronizeWebviewContainerDimensions(this.input.webview, dimension); - this.input.webview.layout(); } } public focus(): void { super.focus(); - if (!this._onFocusWindowHandler.value) { + if (!this._onFocusWindowHandler.value && !isWeb) { // Make sure we restore focus when switching back to a VS Code window this._onFocusWindowHandler.value = this._hostService.onDidChangeFocus(focused => { if (focused && this._editorService.activeControl === this) { @@ -117,22 +119,22 @@ export class WebviewEditor extends BaseEditor { } protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { - const webview = this.input && (this.input as WebviewInput).webview; - if (webview) { + if (this.input instanceof WebviewInput) { + const webview = this.input.webview; if (visible) { webview.claim(this); } else { webview.release(this); } - this.claimWebview(this.input as WebviewInput); + this.claimWebview(this.input); } - super.setEditorVisible(visible, group); } public clearInput() { if (this.input && this.input instanceof WebviewInput) { this.input.webview.release(this); + this._webviewVisibleDisposables.clear(); } super.clearInput(); @@ -177,8 +179,35 @@ export class WebviewEditor extends BaseEditor { this._content.setAttribute('aria-flowto', input.webview.container.id); } + this._webviewVisibleDisposables.clear(); + + // Webviews are not part of the normal editor dom, so we have to register our own drag and drop handler on them. + if (this._editorGroupsService instanceof EditorPart) { + this._webviewVisibleDisposables.add(this._editorGroupsService.createEditorDropTarget(input.webview.container, { + groupContainsPredicate: (group) => this.group?.id === group.group.id + })); + } + + this._webviewVisibleDisposables.add(DOM.addDisposableListener(window, DOM.EventType.DRAG_START, () => { + if (this.input instanceof WebviewInput) { + this.input.webview.windowDidDragStart(); + } + })); + + const onDragEnd = () => { + if (this.input instanceof WebviewInput) { + this.input.webview.windowDidDragEnd(); + } + }; + this._webviewVisibleDisposables.add(DOM.addDisposableListener(window, DOM.EventType.DRAG_END, onDragEnd)); + this._webviewVisibleDisposables.add(DOM.addDisposableListener(window, DOM.EventType.MOUSE_MOVE, currentEvent => { + if (currentEvent.buttons === 0) { + onDragEnd(); + } + })); + this.synchronizeWebviewContainerDimensions(input.webview); - this.trackFocus(input.webview); + this._webviewVisibleDisposables.add(this.trackFocus(input.webview)); } private synchronizeWebviewContainerDimensions(webview: WebviewEditorOverlay, dimension?: DOM.Dimension) { @@ -187,15 +216,17 @@ export class WebviewEditor extends BaseEditor { } } - private trackFocus(webview: WebviewEditorOverlay): void { - this._webviewFocusTrackerDisposables.clear(); + private trackFocus(webview: WebviewEditorOverlay): IDisposable { + const store = new DisposableStore(); // Track focus in webview content const webviewContentFocusTracker = DOM.trackFocus(webview.container); - this._webviewFocusTrackerDisposables.add(webviewContentFocusTracker); - this._webviewFocusTrackerDisposables.add(webviewContentFocusTracker.onDidFocus(() => this._onDidFocusWebview.fire())); + store.add(webviewContentFocusTracker); + store.add(webviewContentFocusTracker.onDidFocus(() => this._onDidFocusWebview.fire())); // Track focus in webview element - this._webviewFocusTrackerDisposables.add(webview.onDidFocus(() => this._onDidFocusWebview.fire())); + store.add(webview.onDidFocus(() => this._onDidFocusWebview.fire())); + + return store; } } diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts index 309a3c86fb..515a3474df 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts @@ -2,13 +2,15 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + import * as dom from 'vs/base/browser/dom'; import { memoize } from 'vs/base/common/decorators'; +import { Lazy } from 'vs/base/common/lazy'; +import { UnownedDisposable as Unowned } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { EditorInput, EditorModel, GroupIdentifier, IEditorInput, Verbosity } from 'vs/workbench/common/editor'; import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; -import { UnownedDisposable as Unowned } from 'vs/base/common/lifecycle'; const WebviewPanelResourceScheme = 'webview-panel'; @@ -41,8 +43,7 @@ class WebviewIconsManager { const webviewSelector = `.show-file-icons .webview-${key}-name-file-icon::before`; if (URI.isUri(value)) { cssRules.push(`${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value)}; }`); - } - else { + } else { cssRules.push(`.vs ${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value.light)}; }`); cssRules.push(`.vs-dark ${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value.dark)}; }`); } @@ -60,19 +61,19 @@ export class WebviewInput extends EditorInput { private _name: string; private _iconPath?: { light: URI, dark: URI }; private _group?: GroupIdentifier; - private readonly _webview: WebviewEditorOverlay; + private readonly _webview: Lazy; constructor( public readonly id: string, public readonly viewType: string, name: string, - webview: Unowned + webview: Lazy> ) { super(); this._name = name; - this._webview = this._register(webview.acquire()); // The input owns this webview + this._webview = webview.map(value => this._register(value.acquire())); // The input owns this webview } public getTypeId(): string { @@ -94,7 +95,7 @@ export class WebviewInput extends EditorInput { return this.getName(); } - public getDescription() { + public getDescription(): string | undefined { return undefined; } @@ -103,12 +104,12 @@ export class WebviewInput extends EditorInput { this._onDidChangeLabel.fire(); } - public get webview() { - return this._webview; + public get webview(): WebviewEditorOverlay { + return this._webview.getValue(); } public get extension() { - return this._webview.extension; + return this._webview.getValue().extension; } public get iconPath() { @@ -128,6 +129,10 @@ export class WebviewInput extends EditorInput { return this._group; } + public updateGroup(group: GroupIdentifier): void { + this._group = group; + } + public async resolve(): Promise { return new EditorModel(); } @@ -135,30 +140,4 @@ export class WebviewInput extends EditorInput { public supportsSplitEditor() { return false; } - - public updateGroup(group: GroupIdentifier): void { - this._group = group; - } -} - -export class RevivedWebviewEditorInput extends WebviewInput { - private _revived: boolean = false; - - constructor( - id: string, - viewType: string, - name: string, - private readonly reviver: (input: WebviewInput) => Promise, - webview: Unowned - ) { - super(id, viewType, name, webview); - } - - public async resolve(): Promise { - if (!this._revived) { - this._revived = true; - await this.reviver(this); - } - return super.resolve(); - } } diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts index 45395faf09..c0239fd121 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts @@ -9,7 +9,7 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorInputFactory } from 'vs/workbench/common/editor'; import { WebviewInput } from './webviewEditorInput'; -import { IWebviewEditorService, WebviewInputOptions } from './webviewEditorService'; +import { IWebviewWorkbenchService, WebviewInputOptions } from './webviewWorkbenchService'; interface SerializedIconPath { light: string | UriComponents; @@ -33,11 +33,11 @@ export class WebviewEditorInputFactory implements IEditorInputFactory { public static readonly ID = WebviewInput.typeId; public constructor( - @IWebviewEditorService private readonly _webviewService: IWebviewEditorService + @IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService ) { } public serialize(input: WebviewInput): string | undefined { - if (!this._webviewService.shouldPersist(input)) { + if (!this._webviewWorkbenchService.shouldPersist(input)) { return undefined; } @@ -54,7 +54,7 @@ export class WebviewEditorInputFactory implements IEditorInputFactory { serializedEditorInput: string ): WebviewInput { const data = this.fromJson(serializedEditorInput); - return this._webviewService.reviveWebview(data.id || generateUuid(), data.viewType, data.title, data.iconPath, data.state, data.options, data.extensionLocation ? { + return this._webviewWorkbenchService.reviveWebview(data.id || generateUuid(), data.viewType, data.title, data.iconPath, data.state, data.options, data.extensionLocation && data.extensionId ? { location: data.extensionLocation, id: data.extensionId } : undefined, data.group); diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index accadd5af9..3f5a9640f6 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -3,56 +3,40 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { addClass, addDisposableListener } from 'vs/base/browser/dom'; -import { Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { addDisposableListener } from 'vs/base/browser/dom'; +import { IDisposable } from 'vs/base/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; -import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { Webview, WebviewContentOptions, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; -import { areWebviewInputOptionsEqual } from 'vs/workbench/contrib/webview/browser/webviewEditorService'; import { WebviewPortMappingManager } from 'vs/workbench/contrib/webview/common/portMapping'; import { loadLocalResource } from 'vs/workbench/contrib/webview/common/resourceLoader'; -import { getWebviewThemeData } from 'vs/workbench/contrib/webview/common/themeing'; +import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/common/themeing'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { BaseWebview, WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/baseWebviewElement'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -interface WebviewContent { - readonly html: string; - readonly options: WebviewContentOptions; - readonly state: string | undefined; -} - -export class IFrameWebview extends Disposable implements Webview { - private element?: HTMLIFrameElement; - - private readonly _ready: Promise; - - private content: WebviewContent; - private _focused = false; - +export class IFrameWebview extends BaseWebview implements Webview { private readonly _portMappingManager: WebviewPortMappingManager; - public extension: { - readonly location: URI; - readonly id?: ExtensionIdentifier; - } | undefined; - constructor( - private readonly id: string, + id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, - @IThemeService themeService: IThemeService, + webviewThemeDataProvider: WebviewThemeDataProvider, @ITunnelService tunnelService: ITunnelService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IFileService private readonly fileService: IFileService, @IConfigurationService private readonly _configurationService: IConfigurationService, + @ITelemetryService telemetryService: ITelemetryService, + @IEnvironmentService environementService: IEnvironmentService, + @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService, ) { - super(); - if (!this.useExternalEndpoint && (!environmentService.options || typeof environmentService.webviewExternalEndpoint !== 'string')) { + super(id, options, contentOptions, webviewThemeDataProvider, telemetryService, environementService, workbenchEnvironmentService); + + if (!this.useExternalEndpoint && (!workbenchEnvironmentService.options || typeof workbenchEnvironmentService.webviewExternalEndpoint !== 'string')) { throw new Error('To use iframe based webviews, you must configure `environmentService.webviewExternalEndpoint`'); } @@ -62,96 +46,31 @@ export class IFrameWebview extends Disposable implements Webview { tunnelService )); - this.content = { - html: '', - options: contentOptions, - state: undefined - }; - - this.element = document.createElement('iframe'); - this.element.className = `webview ${options.customClasses}`; - this.element.sandbox.add('allow-scripts', 'allow-same-origin'); - this.element.setAttribute('src', `${this.externalEndpoint}/index.html?id=${this.id}`); - this.element.style.border = 'none'; - this.element.style.width = '100%'; - this.element.style.height = '100%'; - - this._register(addDisposableListener(window, 'message', e => { - if (!e || !e.data || e.data.target !== this.id) { - return; - } - - switch (e.data.channel) { - case 'onmessage': - if (e.data.data) { - this._onMessage.fire(e.data.data); - } - return; - - case 'did-click-link': - const uri = e.data.data; - this._onDidClickLink.fire(URI.parse(uri)); - return; - - case 'did-scroll': - // if (e.args && typeof e.args[0] === 'number') { - // this._onDidScroll.fire({ scrollYPercentage: e.args[0] }); - // } - return; - - case 'do-reload': - this.reload(); - return; - - case 'do-update-state': - const state = e.data.data; - this.state = state; - this._onDidUpdateState.fire(state); - return; - - case 'did-focus': - this.handleFocusChange(true); - return; - - case 'did-blur': - this.handleFocusChange(false); - return; - - case 'load-resource': - { - const rawPath = e.data.data.path; - const normalizedPath = decodeURIComponent(rawPath); - const uri = URI.parse(normalizedPath.replace(/^\/(\w+)\/(.+)$/, (_, scheme, path) => scheme + ':/' + path)); - this.loadResource(rawPath, uri); - return; - } - - case 'load-localhost': - { - this.localLocalhost(e.data.data.origin); - return; - } - } + this._register(this.on(WebviewMessageChannels.loadResource, (entry: any) => { + const rawPath = entry.path; + const normalizedPath = decodeURIComponent(rawPath); + const uri = URI.parse(normalizedPath.replace(/^\/(\w+)\/(.+)$/, (_, scheme, path) => scheme + ':/' + path)); + this.loadResource(rawPath, uri); })); - this._ready = new Promise(resolve => { - const subscription = this._register(addDisposableListener(window, 'message', (e) => { - if (e.data && e.data.target === this.id && e.data.channel === 'webview-ready') { - if (this.element) { - addClass(this.element, 'ready'); - } - subscription.dispose(); - resolve(); - } - })); - }); + this._register(this.on(WebviewMessageChannels.loadLocalhost, (entry: any) => { + this.localLocalhost(entry.origin); + })); + } - this.style(themeService.getTheme()); - this._register(themeService.onThemeChange(this.style, this)); + protected createElement(options: WebviewOptions) { + const element = document.createElement('iframe'); + element.className = `webview ${options.customClasses}`; + element.sandbox.add('allow-scripts', 'allow-same-origin'); + element.setAttribute('src', `${this.externalEndpoint}/index.html?id=${this.id}`); + element.style.border = 'none'; + element.style.width = '100%'; + element.style.height = '100%'; + return element; } private get externalEndpoint(): string { - const endpoint = this.environmentService.webviewExternalEndpoint!.replace('{{uuid}}', this.id); + const endpoint = this.workbenchEnvironmentService.webviewExternalEndpoint!.replace('{{uuid}}', this.id); if (endpoint[endpoint.length - 1] === '/') { return endpoint.slice(0, endpoint.length - 1); } @@ -168,111 +87,29 @@ export class IFrameWebview extends Disposable implements Webview { } } - public set contentOptions(options: WebviewContentOptions) { - if (areWebviewInputOptionsEqual(options, this.content.options)) { - return; - } - - this.content = { - html: this.content.html, - options: options, - state: this.content.state, - }; - this.doUpdateContent(); - } - public set html(value: string) { - this.content = { - html: this.preprocessHtml(value), - options: this.content.options, - state: this.content.state, - }; - this.doUpdateContent(); + super.html = this.preprocessHtml(value); } private preprocessHtml(value: string): string { - return value.replace(/(["'])vscode-resource:([^\s'"]+?)(["'])/gi, (_, startQuote, path, endQuote) => - `${startQuote}${this.externalEndpoint}/vscode-resource/file${path}${endQuote}`); - } - - public update(html: string, options: WebviewContentOptions, retainContextWhenHidden: boolean) { - if (retainContextWhenHidden && html === this.content.html && areWebviewInputOptionsEqual(options, this.content.options)) { - return; - } - this.content = { - html: this.preprocessHtml(html), - options: options, - state: this.content.state, - }; - this.doUpdateContent(); - } - - private doUpdateContent() { - this._send('content', { - contents: this.content.html, - options: this.content.options, - state: this.content.state, - endpoint: this.externalEndpoint, + return value.replace(/(["'])vscode-resource:(\/\/([^\s'"]+?)(?=\/))?([^\s'"]+?)(["'])/gi, (_, startQuote, _1, scheme, path, endQuote) => { + return `${startQuote}${this.externalEndpoint}/vscode-resource/${scheme || ''}${path}${endQuote}`; }); } - private handleFocusChange(isFocused: boolean): void { - this._focused = isFocused; - if (this._focused) { - this._onDidFocus.fire(); - } - } - - initialScrollProgress: number = 0; - - private readonly _onDidFocus = this._register(new Emitter()); - public readonly onDidFocus = this._onDidFocus.event; - - private readonly _onDidClickLink = this._register(new Emitter()); - public readonly onDidClickLink = this._onDidClickLink.event; - - private readonly _onDidScroll = this._register(new Emitter<{ scrollYPercentage: number }>()); - public readonly onDidScroll = this._onDidScroll.event; - - private readonly _onDidUpdateState = this._register(new Emitter()); - public readonly onDidUpdateState = this._onDidUpdateState.event; - - private readonly _onMessage = this._register(new Emitter()); - public readonly onMessage = this._onMessage.event; - - private readonly _onMissingCsp = this._register(new Emitter()); - public readonly onMissingCsp = this._onMissingCsp.event; - - - sendMessage(data: any): void { - this._send('message', data); - } - - layout(): void { - // noop + protected get extraContentOptions() { + return { + endpoint: this.externalEndpoint, + }; } focus(): void { + console.log('focus'); if (this.element) { - this.element.focus(); + this._send('focus'); } } - dispose(): void { - if (this.element) { - if (this.element.parentElement) { - this.element.parentElement.removeChild(this.element); - } - } - - this.element = undefined!; - super.dispose(); - } - - reload(): void { - this.doUpdateContent(); - } - showFind(): void { throw new Error('Method not implemented.'); } @@ -285,33 +122,6 @@ export class IFrameWebview extends Disposable implements Webview { throw new Error('Method not implemented.'); } - public set state(state: string | undefined) { - this.content = { - html: this.content.html, - options: this.content.options, - state, - }; - } - - private _send(channel: string, data: any): void { - this._ready - .then(() => { - if (!this.element) { - return; - } - this.element.contentWindow!.postMessage({ - channel: channel, - args: data - }, '*'); - }) - .catch(err => console.error(err)); - } - - private style(theme: ITheme): void { - const { styles, activeTheme } = getWebviewThemeData(theme, this._configurationService); - this._send('styles', { styles, activeTheme }); - } - private async loadResource(requestPath: string, uri: URI) { try { const result = await loadLocalResource(uri, this.fileService, this.extension ? this.extension.location : undefined, @@ -331,7 +141,7 @@ export class IFrameWebview extends Disposable implements Webview { return this._send('did-load-resource', { status: 404, - path: uri.path + path: requestPath }); } @@ -342,4 +152,21 @@ export class IFrameWebview extends Disposable implements Webview { location: redirect }); } + + protected postMessage(channel: string, data?: any): void { + if (this.element) { + this.element.contentWindow!.postMessage({ channel, args: data }, '*'); + } + } + + protected on(channel: WebviewMessageChannels, handler: (data: T) => void): IDisposable { + return addDisposableListener(window, 'message', e => { + if (!e || !e.data || e.data.target !== this.id) { + return; + } + if (e.data.channel === channel) { + handler(e.data.data); + } + }); + } } diff --git a/src/vs/workbench/contrib/webview/browser/webviewService.ts b/src/vs/workbench/contrib/webview/browser/webviewService.ts index c4953c13cf..03feaf48d6 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewService.ts @@ -3,25 +3,30 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement'; -import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; -import { DynamicWebviewEditorOverlay } from './dynamicWebviewEditorOverlay'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; +import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement'; +import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/common/themeing'; +import { DynamicWebviewEditorOverlay } from './dynamicWebviewEditorOverlay'; export class WebviewService implements IWebviewService { _serviceBrand: undefined; + private readonly _webviewThemeDataProvider: WebviewThemeDataProvider; + constructor( @IInstantiationService private readonly _instantiationService: IInstantiationService, - ) { } + ) { + this._webviewThemeDataProvider = this._instantiationService.createInstance(WebviewThemeDataProvider); + } createWebview( id: string, options: WebviewOptions, contentOptions: WebviewContentOptions ): WebviewElement { - return this._instantiationService.createInstance(IFrameWebview, id, options, contentOptions); + return this._instantiationService.createInstance(IFrameWebview, id, options, contentOptions, this._webviewThemeDataProvider); } createWebviewEditorOverlay( diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts b/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts similarity index 66% rename from src/vs/workbench/contrib/webview/browser/webviewEditorService.ts rename to src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts index b6cee607c3..5bfae2a712 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts @@ -4,85 +4,28 @@ *--------------------------------------------------------------------------------------------*/ import { equals } from 'vs/base/common/arrays'; +import { memoize } from 'vs/base/common/decorators'; import { IDisposable, toDisposable, UnownedDisposable } from 'vs/base/common/lifecycle'; import { values } from 'vs/base/common/map'; +import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { EditorActivation, IEditorModel } from 'vs/platform/editor/common/editor'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { GroupIdentifier } from 'vs/workbench/common/editor'; -import { IWebviewService, WebviewOptions, WebviewContentOptions } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewOptions, WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; -import { RevivedWebviewEditorInput, WebviewInput } from './webviewEditorInput'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { EditorActivation } from 'vs/platform/editor/common/editor'; +import { WebviewInput } from './webviewEditorInput'; +import { Lazy } from 'vs/base/common/lazy'; -export const IWebviewEditorService = createDecorator('webviewEditorService'); +export const IWebviewWorkbenchService = createDecorator('webviewEditorService'); export interface ICreateWebViewShowOptions { group: IEditorGroup | GroupIdentifier | ACTIVE_GROUP_TYPE | SIDE_GROUP_TYPE; preserveFocus: boolean; } -export interface IWebviewEditorService { - _serviceBrand: undefined; - - createWebview( - id: string, - viewType: string, - title: string, - showOptions: ICreateWebViewShowOptions, - options: WebviewInputOptions, - extension: undefined | { - location: URI, - id: ExtensionIdentifier - }, - ): WebviewInput; - - reviveWebview( - id: string, - viewType: string, - title: string, - iconPath: { light: URI, dark: URI } | undefined, - state: any, - options: WebviewInputOptions, - extension: undefined | { - readonly location: URI, - readonly id?: ExtensionIdentifier - }, - group: number | undefined - ): WebviewInput; - - revealWebview( - webview: WebviewInput, - group: IEditorGroup, - preserveFocus: boolean - ): void; - - registerResolver( - reviver: WebviewResolve - ): IDisposable; - - shouldPersist( - input: WebviewInput - ): boolean; - - resolveWebview( - webview: WebviewInput, - ): Promise; -} - -export interface WebviewResolve { - canResolve( - webview: WebviewInput, - ): boolean; - - resolveWebview( - webview: WebviewInput, - ): Promise; -} - export interface WebviewInputOptions extends WebviewOptions, WebviewContentOptions { readonly tryRestoreScrollPosition?: boolean; readonly retainContextWhenHidden?: boolean; @@ -95,17 +38,89 @@ export function areWebviewInputOptionsEqual(a: WebviewInputOptions, b: WebviewIn && a.allowScripts === b.allowScripts && a.retainContextWhenHidden === b.retainContextWhenHidden && a.tryRestoreScrollPosition === b.tryRestoreScrollPosition - && (a.localResourceRoots === b.localResourceRoots || (Array.isArray(a.localResourceRoots) && Array.isArray(b.localResourceRoots) && equals(a.localResourceRoots, b.localResourceRoots, (a, b) => a.toString() === b.toString()))) - && (a.portMapping === b.portMapping || (Array.isArray(a.portMapping) && Array.isArray(b.portMapping) && equals(a.portMapping, b.portMapping, (a, b) => a.extensionHostPort === b.extensionHostPort && a.webviewPort === b.webviewPort))); + && equals(a.localResourceRoots, b.localResourceRoots, isEqual) + && equals(a.portMapping, b.portMapping, (a, b) => a.extensionHostPort === b.extensionHostPort && a.webviewPort === b.webviewPort); } -function canRevive(reviver: WebviewResolve, webview: WebviewInput): boolean { +export interface IWebviewWorkbenchService { + _serviceBrand: undefined; + + createWebview( + id: string, + viewType: string, + title: string, + showOptions: ICreateWebViewShowOptions, + options: WebviewInputOptions, + extension: WebviewExtensionDescription | undefined, + ): WebviewInput; + + reviveWebview( + id: string, + viewType: string, + title: string, + iconPath: { light: URI, dark: URI } | undefined, + state: any, + options: WebviewInputOptions, + extension: WebviewExtensionDescription | undefined, + group: number | undefined + ): WebviewInput; + + revealWebview( + webview: WebviewInput, + group: IEditorGroup, + preserveFocus: boolean + ): void; + + registerResolver( + resolver: WebviewResolver + ): IDisposable; + + shouldPersist( + input: WebviewInput + ): boolean; + + resolveWebview( + webview: WebviewInput, + ): Promise; +} + +export interface WebviewResolver { + canResolve( + webview: WebviewInput, + ): boolean; + + resolveWebview( + webview: WebviewInput, + ): Promise; +} + +function canRevive(reviver: WebviewResolver, webview: WebviewInput): boolean { if (webview.isDisposed()) { return false; } return reviver.canResolve(webview); } + +export class LazilyResolvedWebviewEditorInput extends WebviewInput { + constructor( + id: string, + viewType: string, + name: string, + webview: Lazy>, + @IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService, + ) { + super(id, viewType, name, webview); + } + + @memoize + public async resolve(): Promise { + await this._webviewWorkbenchService.resolveWebview(this); + return super.resolve(); + } +} + + class RevivalPool { private _awaitingRevival: Array<{ input: WebviewInput, resolve: () => void }> = []; @@ -113,7 +128,7 @@ class RevivalPool { this._awaitingRevival.push({ input, resolve }); } - public reviveFor(reviver: WebviewResolve) { + public reviveFor(reviver: WebviewResolver) { const toRevive = this._awaitingRevival.filter(({ input }) => canRevive(reviver, input)); this._awaitingRevival = this._awaitingRevival.filter(({ input }) => !canRevive(reviver, input)); @@ -123,18 +138,16 @@ class RevivalPool { } } -export class WebviewEditorService implements IWebviewEditorService { +export class WebviewEditorService implements IWebviewWorkbenchService { _serviceBrand: undefined; - private readonly _revivers = new Set(); + private readonly _revivers = new Set(); private readonly _revivalPool = new RevivalPool(); constructor( @IEditorService private readonly _editorService: IEditorService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IWebviewService private readonly _webviewService: IWebviewService, - @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, ) { } public createWebview( @@ -143,14 +156,10 @@ export class WebviewEditorService implements IWebviewEditorService { title: string, showOptions: ICreateWebViewShowOptions, options: WebviewInputOptions, - extension: undefined | { - location: URI, - id: ExtensionIdentifier - }, + extension: WebviewExtensionDescription | undefined, ): WebviewInput { - const webview = this.createWebiew(id, extension, options); - - const webviewInput = this._instantiationService.createInstance(WebviewInput, id, viewType, title, new UnownedDisposable(webview), undefined); + const webview = new Lazy(() => new UnownedDisposable(this.createWebiew(id, extension, options))); + const webviewInput = new WebviewInput(id, viewType, title, webview); this._editorService.openEditor(webviewInput, { pinned: true, preserveFocus: showOptions.preserveFocus, @@ -188,28 +197,16 @@ export class WebviewEditorService implements IWebviewEditorService { iconPath: { light: URI, dark: URI } | undefined, state: any, options: WebviewInputOptions, - extension: undefined | { - readonly location: URI, - readonly id: ExtensionIdentifier - }, + extension: WebviewExtensionDescription | undefined, group: number | undefined, ): WebviewInput { - const webview = this.createWebiew(id, extension, options); - webview.state = state; - - const webviewInput = new RevivedWebviewEditorInput(id, viewType, title, async (webview: WebviewInput): Promise => { - const didRevive = await this.tryRevive(webview); - if (didRevive) { - return Promise.resolve(undefined); - } - - // A reviver may not be registered yet. Put into pool and resolve promise when we can revive - let resolve: () => void; - const promise = new Promise(r => { resolve = r; }); - this._revivalPool.add(webview, resolve!); - return promise; - }, new UnownedDisposable(webview)); + const webview = new Lazy(() => { + const webview = this.createWebiew(id, extension, options); + webview.state = state; + return new UnownedDisposable(webview); + }); + const webviewInput = new LazilyResolvedWebviewEditorInput(id, viewType, title, webview, this); webviewInput.iconPath = iconPath; if (typeof group === 'number') { @@ -219,7 +216,7 @@ export class WebviewEditorService implements IWebviewEditorService { } public registerResolver( - reviver: WebviewResolve + reviver: WebviewResolver ): IDisposable { this._revivers.add(reviver); this._revivalPool.reviveFor(reviver); @@ -238,7 +235,7 @@ export class WebviewEditorService implements IWebviewEditorService { // Revived webviews may not have an actively registered reviver but we still want to presist them // since a reviver should exist when it is actually needed. - return webview instanceof RevivedWebviewEditorInput; + return webview instanceof LazilyResolvedWebviewEditorInput; } private async tryRevive( @@ -258,32 +255,22 @@ export class WebviewEditorService implements IWebviewEditorService { ): Promise { const didRevive = await this.tryRevive(webview); if (!didRevive) { - this._revivalPool.add(webview, () => { }); + // A reviver may not be registered yet. Put into pool and resolve promise when we can revive + let resolve: () => void; + const promise = new Promise(r => { resolve = r; }); + this._revivalPool.add(webview, resolve!); + return promise; } } - private createWebiew(id: string, extension: { location: URI; id: ExtensionIdentifier; } | undefined, options: WebviewInputOptions) { + private createWebiew(id: string, extension: WebviewExtensionDescription | undefined, options: WebviewInputOptions) { const webview = this._webviewService.createWebviewEditorOverlay(id, { enableFindWidget: options.enableFindWidget, retainContextWhenHidden: options.retainContextWhenHidden - }, { - ...options, - localResourceRoots: options.localResourceRoots || this.getDefaultLocalResourceRoots(extension), - }); + }, options); webview.extension = extension; return webview; } - - private getDefaultLocalResourceRoots(extension: undefined | { - location: URI, - id: ExtensionIdentifier - }): URI[] { - const rootPaths = this._contextService.getWorkspace().folders.map(x => x.uri); - if (extension) { - rootPaths.push(extension.location); - } - return rootPaths; - } } -registerSingleton(IWebviewEditorService, WebviewEditorService, true); +registerSingleton(IWebviewWorkbenchService, WebviewEditorService, true); diff --git a/src/vs/workbench/contrib/webview/common/portMapping.ts b/src/vs/workbench/contrib/webview/common/portMapping.ts index cb1cb69d6e..7541317f72 100644 --- a/src/vs/workbench/contrib/webview/common/portMapping.ts +++ b/src/vs/workbench/contrib/webview/common/portMapping.ts @@ -34,6 +34,9 @@ export class WebviewPortMappingManager extends Disposable { if (extensionLocation && extensionLocation.scheme === REMOTE_HOST_SCHEME) { const tunnel = await this.getOrCreateTunnel(mapping.extensionHostPort); if (tunnel) { + if (tunnel.tunnelLocalPort === mapping.webviewPort) { + return undefined; + } return encodeURI(uri.with({ authority: `127.0.0.1:${tunnel.tunnelLocalPort}`, }).toString(true)); diff --git a/src/vs/workbench/contrib/webview/common/resourceLoader.ts b/src/vs/workbench/contrib/webview/common/resourceLoader.ts index cb1990e2eb..cb607a3f13 100644 --- a/src/vs/workbench/contrib/webview/common/resourceLoader.ts +++ b/src/vs/workbench/contrib/webview/common/resourceLoader.ts @@ -60,7 +60,7 @@ export async function loadLocalResource( authority: extensionLocation.authority, path: '/vscode-resource', query: JSON.stringify({ - requestResourcePath: requestUri.path + requestResourcePath: normalizedPath.path }) }); return resolveContent(fileService, redirectedUri, getWebviewContentMimeType(requestUri)); diff --git a/src/vs/workbench/contrib/webview/common/themeing.ts b/src/vs/workbench/contrib/webview/common/themeing.ts index 990e2be3e6..c067b4295f 100644 --- a/src/vs/workbench/contrib/webview/common/themeing.ts +++ b/src/vs/workbench/contrib/webview/common/themeing.ts @@ -3,45 +3,83 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { createMemoizer } from 'vs/base/common/decorators'; +import { Disposable } from 'vs/base/common/lifecycle'; import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; -import { ITheme, LIGHT, DARK } from 'vs/platform/theme/common/themeService'; +import { DARK, ITheme, IThemeService, LIGHT } from 'vs/platform/theme/common/themeService'; +import { Emitter } from 'vs/base/common/event'; interface WebviewThemeData { readonly activeTheme: string; - readonly styles: { readonly [key: string]: string | number }; + readonly styles: { readonly [key: string]: string | number; }; } -export function getWebviewThemeData( - theme: ITheme, - configurationService: IConfigurationService -): WebviewThemeData { - const configuration = configurationService.getValue('editor'); - const editorFontFamily = configuration.fontFamily || EDITOR_FONT_DEFAULTS.fontFamily; - const editorFontWeight = configuration.fontWeight || EDITOR_FONT_DEFAULTS.fontWeight; - const editorFontSize = configuration.fontSize || EDITOR_FONT_DEFAULTS.fontSize; +export class WebviewThemeDataProvider extends Disposable { - const exportedColors = colorRegistry.getColorRegistry().getColors().reduce((colors, entry) => { - const color = theme.getColor(entry.id); - if (color) { - colors['vscode-' + entry.id.replace('.', '-')] = color.toString(); - } - return colors; - }, {} as { [key: string]: string }); - const styles = { - 'vscode-font-family': '-apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif', - 'vscode-font-weight': 'normal', - 'vscode-font-size': '13px', - 'vscode-editor-font-family': editorFontFamily, - 'vscode-editor-font-weight': editorFontWeight, - 'vscode-editor-font-size': editorFontSize, - ...exportedColors - }; + private static readonly MEMOIZER = createMemoizer(); - const activeTheme = ApiThemeClassName.fromTheme(theme); - return { styles, activeTheme }; + private readonly _onThemeDataChanged = this._register(new Emitter()); + public readonly onThemeDataChanged = this._onThemeDataChanged.event; + + constructor( + @IThemeService private readonly _themeService: IThemeService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + ) { + super(); + + this._register(this._themeService.onThemeChange(() => { + this.reset(); + })); + + const webviewConfigurationKeys = ['editor.fontFamily', 'editor.fontWeight', 'editor.fontSize']; + this._register(this._configurationService.onDidChangeConfiguration(e => { + if (webviewConfigurationKeys.some(key => e.affectsConfiguration(key))) { + this.reset(); + } + })); + } + + public getTheme(): ITheme { + return this._themeService.getTheme(); + } + + @WebviewThemeDataProvider.MEMOIZER + public getWebviewThemeData(): WebviewThemeData { + const configuration = this._configurationService.getValue('editor'); + const editorFontFamily = configuration.fontFamily || EDITOR_FONT_DEFAULTS.fontFamily; + const editorFontWeight = configuration.fontWeight || EDITOR_FONT_DEFAULTS.fontWeight; + const editorFontSize = configuration.fontSize || EDITOR_FONT_DEFAULTS.fontSize; + + const theme = this._themeService.getTheme(); + const exportedColors = colorRegistry.getColorRegistry().getColors().reduce((colors, entry) => { + const color = theme.getColor(entry.id); + if (color) { + colors['vscode-' + entry.id.replace('.', '-')] = color.toString(); + } + return colors; + }, {} as { [key: string]: string; }); + + const styles = { + 'vscode-font-family': '-apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif', + 'vscode-font-weight': 'normal', + 'vscode-font-size': '13px', + 'vscode-editor-font-family': editorFontFamily, + 'vscode-editor-font-weight': editorFontWeight, + 'vscode-editor-font-size': editorFontSize, + ...exportedColors + }; + + const activeTheme = ApiThemeClassName.fromTheme(theme); + return { styles, activeTheme }; + } + + private reset() { + WebviewThemeDataProvider.MEMOIZER.clear(); + this._onThemeDataChanged.fire(); + } } enum ApiThemeClassName { @@ -52,12 +90,10 @@ enum ApiThemeClassName { namespace ApiThemeClassName { export function fromTheme(theme: ITheme): ApiThemeClassName { - if (theme.type === LIGHT) { - return ApiThemeClassName.light; - } else if (theme.type === DARK) { - return ApiThemeClassName.dark; - } else { - return ApiThemeClassName.highContrast; + switch (theme.type) { + case LIGHT: return ApiThemeClassName.light; + case DARK: return ApiThemeClassName.dark; + default: return ApiThemeClassName.highContrast; } } } diff --git a/src/vs/workbench/contrib/webview/electron-browser/pre/electron-index.js b/src/vs/workbench/contrib/webview/electron-browser/pre/electron-index.js index 07f7d132d5..59edaba961 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/pre/electron-index.js +++ b/src/vs/workbench/contrib/webview/electron-browser/pre/electron-index.js @@ -13,15 +13,6 @@ return; } hasRegistered = true; - - // @ts-ignore - require('electron').webFrame.registerURLSchemeAsPrivileged('vscode-resource', { - secure: true, - bypassCSP: false, - allowServiceWorkers: false, - supportFetchAPI: true, - corsEnabled: true - }); }; }()); diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index 738ce026e6..b3963111b9 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -4,42 +4,69 @@ *--------------------------------------------------------------------------------------------*/ import { FindInPageOptions, OnBeforeRequestDetails, OnHeadersReceivedDetails, Response, WebContents, WebviewTag } from 'electron'; -import { addClass, addDisposableListener } from 'vs/base/browser/dom'; +import { addDisposableListener } from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; import { once } from 'vs/base/common/functional'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, toDisposable, IDisposable } from 'vs/base/common/lifecycle'; import { isMacintosh } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import * as modes from 'vs/editor/common/modes'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; -import { Webview, WebviewContentOptions, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; +import { Webview, WebviewContentOptions, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewPortMappingManager } from 'vs/workbench/contrib/webview/common/portMapping'; import { WebviewResourceScheme } from 'vs/workbench/contrib/webview/common/resourceLoader'; -import { getWebviewThemeData } from 'vs/workbench/contrib/webview/common/themeing'; +import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/common/themeing'; import { registerFileProtocol } from 'vs/workbench/contrib/webview/electron-browser/webviewProtocols'; -import { areWebviewInputOptionsEqual } from '../browser/webviewEditorService'; import { WebviewFindDelegate, WebviewFindWidget } from '../browser/webviewFindWidget'; +import { BaseWebview, WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/baseWebviewElement'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -interface IKeydownEvent { - key: string; - keyCode: number; - code: string; - shiftKey: boolean; - altKey: boolean; - ctrlKey: boolean; - metaKey: boolean; - repeat: boolean; + +class WebviewTagHandle extends Disposable { + + private _webContents: undefined | WebContents | 'destroyed'; + + public constructor( + public readonly webview: WebviewTag, + ) { + super(); + + this._register(addDisposableListener(this.webview, 'destroyed', () => { + this._webContents = 'destroyed'; + })); + + this._register(addDisposableListener(this.webview, 'did-start-loading', once(() => { + const contents = this.webContents; + if (contents) { + this._onFirstLoad.fire(contents); + this._register(toDisposable(() => { + contents.removeAllListeners(); + })); + } + }))); + } + + private readonly _onFirstLoad = this._register(new Emitter()); + public readonly onFirstLoad = this._onFirstLoad.event; + + public get webContents(): WebContents | undefined { + if (this._webContents === 'destroyed') { + return undefined; + } + if (this._webContents) { + return this._webContents; + } + this._webContents = this.webview.getWebContents(); + return this._webContents; + } } type OnBeforeRequestDelegate = (details: OnBeforeRequestDetails) => Promise; -type OnHeadersReceivedDelegate = (details: OnHeadersReceivedDetails) => { cancel: boolean } | undefined; +type OnHeadersReceivedDelegate = (details: OnHeadersReceivedDetails) => { cancel: boolean; } | undefined; class WebviewSession extends Disposable { @@ -47,16 +74,11 @@ class WebviewSession extends Disposable { private readonly _onHeadersReceivedDelegates: Array = []; public constructor( - webview: WebviewTag, + webviewHandle: WebviewTagHandle, ) { super(); - this._register(addDisposableListener(webview, 'did-start-loading', once(() => { - const contents = webview.getWebContents(); - if (!contents) { - return; - } - + this._register(webviewHandle.onFirstLoad(contents => { contents.session.webRequest.onBeforeRequest(async (details, callback) => { for (const delegate of this._onBeforeRequestDelegates) { const result = await delegate(details); @@ -78,7 +100,7 @@ class WebviewSession extends Disposable { } callback({ cancel: false, responseHeaders: details.responseHeaders }); }); - }))); + })); } public onBeforeRequest(delegate: OnBeforeRequestDelegate) { @@ -92,26 +114,19 @@ class WebviewSession extends Disposable { class WebviewProtocolProvider extends Disposable { constructor( - webview: WebviewTag, + handle: WebviewTagHandle, private readonly _getExtensionLocation: () => URI | undefined, private readonly _getLocalResourceRoots: () => ReadonlyArray, private readonly _fileService: IFileService, ) { super(); - this._register(addDisposableListener(webview, 'did-start-loading', once(() => { - const contents = webview.getWebContents(); - if (contents) { - this.registerProtocols(contents); - } - }))); + this._register(handle.onFirstLoad(contents => { + this.registerProtocols(contents); + })); } private registerProtocols(contents: WebContents) { - if (contents.isDestroyed()) { - return; - } - registerFileProtocol(contents, WebviewResourceScheme, this._fileService, this._getExtensionLocation(), () => this._getLocalResourceRoots() ); @@ -120,8 +135,6 @@ class WebviewProtocolProvider extends Disposable { class WebviewPortMappingProvider extends Disposable { - private readonly _manager: WebviewPortMappingManager; - constructor( session: WebviewSession, getExtensionLocation: () => URI | undefined, @@ -129,10 +142,10 @@ class WebviewPortMappingProvider extends Disposable { tunnelService: ITunnelService, ) { super(); - this._manager = this._register(new WebviewPortMappingManager(getExtensionLocation, mappings, tunnelService)); + const manager = this._register(new WebviewPortMappingManager(getExtensionLocation, mappings, tunnelService)); session.onBeforeRequest(async details => { - const redirect = await this._manager.getRedirect(details.url); + const redirect = await manager.getRedirect(details.url); return redirect ? { redirectURL: redirect } : undefined; }); } @@ -143,33 +156,23 @@ class WebviewKeyboardHandler extends Disposable { private _ignoreMenuShortcut = false; constructor( - private readonly _webview: WebviewTag + private readonly _webviewHandle: WebviewTagHandle ) { super(); if (this.shouldToggleMenuShortcutsEnablement) { - this._register(addDisposableListener(this._webview, 'did-start-loading', () => { - const contents = this.getWebContents(); - if (contents) { - contents.on('before-input-event', (_event, input) => { - if (input.type === 'keyDown' && document.activeElement === this._webview) { - this._ignoreMenuShortcut = input.control || input.meta; - this.setIgnoreMenuShortcuts(this._ignoreMenuShortcut); - } - }); - } + this._register(_webviewHandle.onFirstLoad(contents => { + contents.on('before-input-event', (_event, input) => { + if (input.type === 'keyDown' && document.activeElement === this._webviewHandle.webview) { + this._ignoreMenuShortcut = input.control || input.meta; + this.setIgnoreMenuShortcuts(this._ignoreMenuShortcut); + } + }); })); } - this._register(addDisposableListener(this._webview, 'ipc-message', (event) => { + this._register(addDisposableListener(this._webviewHandle.webview, 'ipc-message', (event) => { switch (event.channel) { - case 'did-keydown': - // Electron: workaround for https://github.com/electron/electron/issues/14258 - // We have to detect keyboard events in the and dispatch them to our - // keybinding service because these events do not bubble to the parent window anymore. - this.handleKeydown(event.args[0]); - return; - case 'did-focus': this.setIgnoreMenuShortcuts(this._ignoreMenuShortcut); break; @@ -189,103 +192,38 @@ class WebviewKeyboardHandler extends Disposable { if (!this.shouldToggleMenuShortcutsEnablement) { return; } - const contents = this.getWebContents(); + const contents = this._webviewHandle.webContents; if (contents) { contents.setIgnoreMenuShortcuts(value); } } - - private getWebContents(): WebContents | undefined { - const contents = this._webview.getWebContents(); - if (contents && !contents.isDestroyed()) { - return contents; - } - return undefined; - } - - private handleKeydown(event: IKeydownEvent): void { - // Create a fake KeyboardEvent from the data provided - const emulatedKeyboardEvent = new KeyboardEvent('keydown', event); - // Force override the target - Object.defineProperty(emulatedKeyboardEvent, 'target', { - get: () => this._webview - }); - // And re-dispatch - window.dispatchEvent(emulatedKeyboardEvent); - } } -interface WebviewContent { - readonly html: string; - readonly options: WebviewContentOptions; - readonly state: string | undefined; -} - -export class ElectronWebviewBasedWebview extends Disposable implements Webview, WebviewFindDelegate { - private _webview: WebviewTag | undefined; - private _ready: Promise; - +export class ElectronWebviewBasedWebview extends BaseWebview implements Webview, WebviewFindDelegate { private _webviewFindWidget: WebviewFindWidget | undefined; private _findStarted: boolean = false; - private content: WebviewContent; - private _focused = false; - - private readonly _onDidFocus = this._register(new Emitter()); - public readonly onDidFocus: Event = this._onDidFocus.event; - - public extension: { - readonly location: URI; - readonly id?: ExtensionIdentifier; - } | undefined; + public extension: WebviewExtensionDescription | undefined; constructor( + id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, + private readonly _webviewThemeDataProvider: WebviewThemeDataProvider, @IInstantiationService instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, @IFileService fileService: IFileService, @ITunnelService tunnelService: ITunnelService, - @IConfigurationService private readonly _configurationService: IConfigurationService, - @ITelemetryService private readonly _telemetryService: ITelemetryService, - @IEnvironmentService private readonly _environementService: IEnvironmentService, + @ITelemetryService telemetryService: ITelemetryService, + @IEnvironmentService environementService: IEnvironmentService, + @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService, ) { - super(); - this.content = { - html: '', - options: contentOptions, - state: undefined - }; + super(id, options, contentOptions, _webviewThemeDataProvider, telemetryService, environementService, workbenchEnvironmentService); - this._webview = document.createElement('webview'); - this._webview.setAttribute('partition', `webview${Date.now()}`); - this._webview.setAttribute('webpreferences', 'contextIsolation=yes'); - this._webview.className = `webview ${options.customClasses}`; - - this._webview.style.flex = '0 1'; - this._webview.style.width = '0'; - this._webview.style.height = '0'; - this._webview.style.outline = '0'; - - this._webview.preload = require.toUrl('./pre/electron-index.js'); - 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 (this._webview && event.channel === 'webview-ready') { - // console.info('[PID Webview] ' event.args[0]); - addClass(this._webview, 'ready'); // can be found by debug command - - subscription.dispose(); - resolve(); - } - })); - }); - - const session = this._register(new WebviewSession(this._webview)); + const webviewAndContents = this._register(new WebviewTagHandle(this.element!)); + const session = this._register(new WebviewSession(webviewAndContents)); this._register(new WebviewProtocolProvider( - this._webview, + webviewAndContents, () => this.extension ? this.extension.location : undefined, () => (this.content.options.localResourceRoots || []), fileService)); @@ -297,221 +235,101 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview, tunnelService, )); - this._register(new WebviewKeyboardHandler(this._webview)); + this._register(new WebviewKeyboardHandler(webviewAndContents)); - this._register(addDisposableListener(this._webview, 'console-message', function (e: { level: number; message: string; line: number; sourceId: string; }) { + this._register(addDisposableListener(this.element!, 'console-message', function (e: { level: number; message: string; line: number; sourceId: string; }) { console.log(`[Embedded Page] ${e.message}`); })); - this._register(addDisposableListener(this._webview, 'dom-ready', () => { - this.layout(); - + this._register(addDisposableListener(this.element!, 'dom-ready', () => { // Workaround for https://github.com/electron/electron/issues/14474 - if (this._webview && (this._focused || document.activeElement === this._webview)) { - this._webview.blur(); - this._webview.focus(); + if (this.element && (this.focused || document.activeElement === this.element)) { + this.element.blur(); + this.element.focus(); } })); - this._register(addDisposableListener(this._webview, 'crashed', () => { + this._register(addDisposableListener(this.element!, 'crashed', () => { console.error('embedded page crashed'); })); - this._register(addDisposableListener(this._webview, 'ipc-message', (event) => { - if (!this._webview) { + + this._register(this.on('synthetic-mouse-event', (rawEvent: any) => { + if (!this.element) { return; } - - switch (event.channel) { - case 'onmessage': - if (event.args && event.args.length) { - this._onMessage.fire(event.args[0]); - } - return; - - case 'did-click-link': - const [uri] = event.args; - this._onDidClickLink.fire(URI.parse(uri)); - return; - - case 'synthetic-mouse-event': - { - const rawEvent = event.args[0]; - const bounds = this._webview.getBoundingClientRect(); - try { - window.dispatchEvent(new MouseEvent(rawEvent.type, { - ...rawEvent, - clientX: rawEvent.clientX + bounds.left, - clientY: rawEvent.clientY + bounds.top, - })); - return; - } catch { - // CustomEvent was treated as MouseEvent so don't do anything - https://github.com/microsoft/vscode/issues/78915 - return; - } - } - - case 'did-set-content': - this._webview.style.flex = ''; - this._webview.style.width = '100%'; - this._webview.style.height = '100%'; - this.layout(); - return; - - case 'did-scroll': - if (event.args && typeof event.args[0] === 'number') { - this._onDidScroll.fire({ scrollYPercentage: event.args[0] }); - } - return; - - case 'do-reload': - this.reload(); - return; - - case 'do-update-state': - const state = event.args[0]; - this.state = state; - this._onDidUpdateState.fire(state); - return; - - case 'did-focus': - this.handleFocusChange(true); - return; - - case 'did-blur': - this.handleFocusChange(false); - return; - - case 'no-csp-found': - this.handleNoCspFound(); - return; + const bounds = this.element.getBoundingClientRect(); + try { + window.dispatchEvent(new MouseEvent(rawEvent.type, { + ...rawEvent, + clientX: rawEvent.clientX + bounds.left, + clientY: rawEvent.clientY + bounds.top, + })); + return; + } catch { + // CustomEvent was treated as MouseEvent so don't do anything - https://github.com/microsoft/vscode/issues/78915 + return; } })); - this._register(addDisposableListener(this._webview, 'devtools-opened', () => { + this._register(this.on('did-set-content', () => { + if (this.element) { + this.element.style.flex = ''; + this.element.style.width = '100%'; + this.element.style.height = '100%'; + } + })); + + this._register(addDisposableListener(this.element!, 'devtools-opened', () => { this._send('devtools-opened'); })); if (options.enableFindWidget) { this._webviewFindWidget = this._register(instantiationService.createInstance(WebviewFindWidget, this)); - this._register(addDisposableListener(this._webview, 'found-in-page', e => { + this._register(addDisposableListener(this.element!, 'found-in-page', e => { this._hasFindResult.fire(e.result.matches > 0); })); } - - this.style(themeService.getTheme()); - this._register(themeService.onThemeChange(this.style, this)); } + protected createElement(options: WebviewOptions) { + const element = document.createElement('webview'); + element.setAttribute('partition', `webview${Date.now()}`); + element.setAttribute('webpreferences', 'contextIsolation=yes'); + element.className = `webview ${options.customClasses}`; + + element.style.flex = '0 1'; + element.style.width = '0'; + element.style.height = '0'; + element.style.outline = '0'; + + element.preload = require.toUrl('./pre/electron-index.js'); + element.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'; + + return element; + } + + protected readonly extraContentOptions = {}; + public mountTo(parent: HTMLElement) { - if (!this._webview) { + if (!this.element) { return; } if (this._webviewFindWidget) { parent.appendChild(this._webviewFindWidget.getDomNode()!); } - parent.appendChild(this._webview); + parent.appendChild(this.element); } - dispose(): void { - if (this._webview) { - if (this._webview.parentElement) { - this._webview.parentElement.removeChild(this._webview); - } - this._webview = undefined; - } - - if (this._webviewFindWidget) { - this._webviewFindWidget.dispose(); - this._webviewFindWidget = undefined; - } - super.dispose(); - } - - private readonly _onDidClickLink = this._register(new Emitter()); - public readonly onDidClickLink = this._onDidClickLink.event; - - private readonly _onDidScroll = this._register(new Emitter<{ scrollYPercentage: number }>()); - public readonly onDidScroll = this._onDidScroll.event; - - private readonly _onDidUpdateState = this._register(new Emitter()); - public readonly onDidUpdateState = this._onDidUpdateState.event; - - private readonly _onMessage = this._register(new Emitter()); - public readonly onMessage = this._onMessage.event; - - private readonly _onMissingCsp = this._register(new Emitter()); - public readonly onMissingCsp = this._onMissingCsp.event; - - private _send(channel: string, data?: any): void { - this._ready - .then(() => { - if (this._webview) { - this._webview.send(channel, data); - } - }) - .catch(err => console.error(err)); - } - - public set initialScrollProgress(value: number) { - this._send('initial-scroll-position', value); - } - - public set state(state: string | undefined) { - this.content = { - html: this.content.html, - options: this.content.options, - state, - }; - } - - public set contentOptions(options: WebviewContentOptions) { - if (areWebviewInputOptionsEqual(options, this.content.options)) { - return; - } - - this.content = { - html: this.content.html, - options: options, - state: this.content.state, - }; - this.doUpdateContent(); - } - - public set html(value: string) { - this.content = { - html: value, - options: this.content.options, - state: this.content.state, - }; - this.doUpdateContent(); - } - - public update(html: string, options: WebviewContentOptions, retainContextWhenHidden: boolean) { - if (retainContextWhenHidden && html === this.content.html && areWebviewInputOptionsEqual(options, this.content.options)) { - return; - } - this.content = { - html: html, - options: options, - state: this.content.state, - }; - this.doUpdateContent(); - } - - private doUpdateContent() { - this._send('content', { - contents: this.content.html, - options: this.content.options, - state: this.content.state - }); + protected postMessage(channel: string, data?: any): void { + this.element?.send(channel, data); } public focus(): void { - if (!this._webview) { + if (!this.element) { return; } try { - this._webview.focus(); + this.element.focus(); } catch { // noop } @@ -521,78 +339,19 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview, this.handleFocusChange(true); } - private handleFocusChange(isFocused: boolean): void { - this._focused = isFocused; - if (isFocused) { - this._onDidFocus.fire(); - } - } - - private _hasAlertedAboutMissingCsp = false; - - private handleNoCspFound(): void { - if (this._hasAlertedAboutMissingCsp) { - return; - } - this._hasAlertedAboutMissingCsp = true; - - if (this.extension && this.extension.id) { - if (this._environementService.isExtensionDevelopment) { - this._onMissingCsp.fire(this.extension.id); - } - - type TelemetryClassification = { - extension?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' } - }; - type TelemetryData = { - extension?: string, - }; - - this._telemetryService.publicLog2('webviewMissingCsp', { - extension: this.extension.id.value - }); - } - } - - public sendMessage(data: any): void { - this._send('message', data); - } - - private style(theme: ITheme): void { - const { styles, activeTheme } = getWebviewThemeData(theme, this._configurationService); - this._send('styles', { styles, activeTheme }); + protected style(): void { + super.style(); if (this._webviewFindWidget) { - this._webviewFindWidget.updateTheme(theme); + this._webviewFindWidget.updateTheme(this._webviewThemeDataProvider.getTheme()); } } - public layout(): void { - if (!this._webview || this._webview.style.width === '0px') { - return; - } - const contents = this._webview.getWebContents(); - if (!contents || contents.isDestroyed()) { - return; - } - const window = (contents as any).getOwnerBrowserWindow(); - if (!window || !window.webContents || window.webContents.isDestroyed()) { - return; - } - window.webContents.getZoomFactor((factor: number) => { - if (contents.isDestroyed()) { - return; - } - - contents.setZoomFactor(factor); - }); - } - private readonly _hasFindResult = this._register(new Emitter()); public readonly hasFindResult: Event = this._hasFindResult.event; public startFind(value: string, options?: FindInPageOptions) { - if (!value || !this._webview) { + if (!value || !this.element) { return; } @@ -608,7 +367,7 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview, }; this._findStarted = true; - this._webview.findInPage(value, findOptions); + this.element.findInPage(value, findOptions); } /** @@ -619,7 +378,7 @@ export class ElectronWebviewBasedWebview 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) { + if (!this.element) { return; } @@ -634,73 +393,65 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview, return; } - this._webview.findInPage(value, options); + this.element.findInPage(value, options); } public stopFind(keepSelection?: boolean): void { this._hasFindResult.fire(false); - if (!this._webview) { + if (!this.element) { return; } this._findStarted = false; - this._webview.stopFindInPage(keepSelection ? 'keepSelection' : 'clearSelection'); + this.element.stopFindInPage(keepSelection ? 'keepSelection' : 'clearSelection'); } public showFind() { - if (this._webviewFindWidget) { - this._webviewFindWidget.reveal(); - } + this._webviewFindWidget?.reveal(); } public hideFind() { - if (this._webviewFindWidget) { - this._webviewFindWidget.hide(); - } + this._webviewFindWidget?.hide(); } public runFindAction(previous: boolean) { - if (this._webviewFindWidget) { - this._webviewFindWidget.find(previous); - } - } - - public reload() { - this.doUpdateContent(); + this._webviewFindWidget?.find(previous); } public selectAll() { - if (this._webview) { - this._webview.selectAll(); - } + this.element?.selectAll(); } public copy() { - if (this._webview) { - this._webview.copy(); - } + this.element?.copy(); } public paste() { - if (this._webview) { - this._webview.paste(); - } + this.element?.paste(); } public cut() { - if (this._webview) { - this._webview.cut(); - } + this.element?.cut(); } public undo() { - if (this._webview) { - this._webview.undo(); - } + this.element?.undo(); } public redo() { - if (this._webview) { - this._webview.redo(); + this.element?.redo(); + } + + protected on(channel: WebviewMessageChannels | string, handler: (data: T) => void): IDisposable { + if (!this.element) { + return Disposable.None; } + return addDisposableListener(this.element, 'ipc-message', (event) => { + if (!this.element) { + return; + } + if (event.channel === channel && event.args && event.args.length) { + handler(event.args[0]); + } + }); } } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts index d2735ff344..5d2e10b9b8 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts @@ -6,17 +6,22 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DynamicWebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay'; -import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement'; import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; +import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement'; +import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/common/themeing'; import { ElectronWebviewBasedWebview } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; export class ElectronWebviewService implements IWebviewService { _serviceBrand: undefined; + private readonly webviewThemeDataProvider: WebviewThemeDataProvider; + constructor( @IInstantiationService private readonly _instantiationService: IInstantiationService, @IConfigurationService private readonly _configService: IConfigurationService, - ) { } + ) { + this.webviewThemeDataProvider = this._instantiationService.createInstance(WebviewThemeDataProvider); + } createWebview( id: string, @@ -25,9 +30,9 @@ export class ElectronWebviewService implements IWebviewService { ): WebviewElement { const useExternalEndpoint = this._configService.getValue('webview.experimental.useExternalEndpoint'); if (useExternalEndpoint) { - return this._instantiationService.createInstance(IFrameWebview, id, options, contentOptions); + return this._instantiationService.createInstance(IFrameWebview, id, options, contentOptions, this.webviewThemeDataProvider); } else { - return this._instantiationService.createInstance(ElectronWebviewBasedWebview, options, contentOptions); + return this._instantiationService.createInstance(ElectronWebviewBasedWebview, id, options, contentOptions, this.webviewThemeDataProvider); } } diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/openWebsite.ts b/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/openWebsite.ts deleted file mode 100644 index 2409dbeeb4..0000000000 --- a/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/openWebsite.ts +++ /dev/null @@ -1,72 +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 } from 'vs/workbench/common/contributions'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { ITelemetryService, ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import * as platform from 'vs/base/common/platform'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { URI } from 'vs/base/common/uri'; -import { IProductService } from 'vs/platform/product/common/productService'; - -export class OpenWelcomePageInBrowser implements IWorkbenchContribution { - - private static readonly hideWelcomeSettingskey = 'workbench.hide.welcome'; - - private welcomePageURL?: string; - private appName: string; - - constructor( - @IStorageService private readonly storageService: IStorageService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @ITelemetryService private readonly telemetryService: ITelemetryService, - @IOpenerService private readonly openerService: IOpenerService, - @IProductService productService: IProductService - ) { - this.appName = productService.nameLong; - this.welcomePageURL = productService.welcomePage; - - if ( - !productService.welcomePage || - environmentService.skipGettingStarted || - environmentService.isExtensionDevelopment - ) { - return; - } - - this.handleWelcome(); - } - - private getUrl(telemetryInfo: ITelemetryInfo): string { - return `${this.welcomePageURL}&&from=${this.appName}&&id=${telemetryInfo.machineId}`; - } - - private openExternal(url: string) { - // Don't open the welcome page as the root user on Linux, this is due to a bug with xdg-open - // which recommends against running itself as root. - if (platform.isLinux && platform.isRootUser()) { - return; - } - this.openerService.open(URI.parse(url)); - } - - private handleWelcome(): void { - //make sure the user is online, otherwise refer to the next run to show the welcome page - if (!navigator.onLine) { - return; - } - - let firstStartup = !this.storageService.get(OpenWelcomePageInBrowser.hideWelcomeSettingskey, StorageScope.GLOBAL); - - if (firstStartup && this.welcomePageURL) { - this.telemetryService.getTelemetryInfo().then(info => { - let url = this.getUrl(info); - this.openExternal(url); - this.storageService.store(OpenWelcomePageInBrowser.hideWelcomeSettingskey, true, StorageScope.GLOBAL); - }); - } - } -} diff --git a/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts index 817e506588..110372fa68 100644 --- a/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts +++ b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts @@ -212,7 +212,7 @@ class WelcomeOverlay extends Disposable { } private updateProblemsKey() { - const problems = document.querySelector('div[id="workbench.parts.statusbar"] .statusbar-item.left .octicon.octicon-warning'); + const problems = document.querySelector('div[id="workbench.parts.statusbar"] .statusbar-item.left .codicon.codicon-warning'); const key = this._overlay.querySelector('.key.problems') as HTMLElement; if (problems instanceof HTMLElement) { const target = problems.getBoundingClientRect(); @@ -222,8 +222,8 @@ class WelcomeOverlay extends Disposable { key.style.bottom = bottom + 'px'; key.style.left = left + 'px'; } else { - key.style.bottom = null; - key.style.left = null; + key.style.bottom = ''; + key.style.left = ''; } } diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index 3b2345a212..6464b69d23 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -148,7 +148,6 @@ interface ExtensionSuggestion { const extensionPacks: ExtensionSuggestion[] = [ { name: localize('welcomePage.javaScript', "JavaScript"), id: 'dbaeumer.vscode-eslint' }, - { name: localize('welcomePage.typeScript', "TypeScript"), id: 'ms-vscode.vscode-typescript-tslint-plugin' }, { name: localize('welcomePage.python', "Python"), id: 'ms-python.python' }, // { name: localize('welcomePage.go', "Go"), id: 'lukehoban.go' }, { name: localize('welcomePage.php', "PHP"), id: 'felixfbecker.php-pack' }, diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution.ts b/src/vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.contribution.ts similarity index 95% rename from src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution.ts rename to src/vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.contribution.ts index db844f2a36..9556bded7c 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution.ts +++ b/src/vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Registry } from 'vs/platform/registry/common/platform'; -import { BrowserTelemetryOptOut } from 'vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut'; +import { BrowserTelemetryOptOut } from 'vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut.ts b/src/vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.ts similarity index 66% rename from src/vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut.ts rename to src/vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.ts index 64323f2b22..ffba540947 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut.ts +++ b/src/vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.ts @@ -10,7 +10,6 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; -import { onUnexpectedError } from 'vs/base/common/errors'; import { IExperimentService, ExperimentState } from 'vs/workbench/contrib/experiments/common/experimentService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { language, locale } from 'vs/base/common/platform'; @@ -21,57 +20,60 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; export abstract class AbstractTelemetryOptOut implements IWorkbenchContribution { - private static TELEMETRY_OPT_OUT_SHOWN = 'workbench.telemetryOptOutShown'; + private static readonly TELEMETRY_OPT_OUT_SHOWN = 'workbench.telemetryOptOutShown'; private privacyUrl: string | undefined; constructor( - @IStorageService storageService: IStorageService, - @IOpenerService openerService: IOpenerService, + @IStorageService private readonly storageService: IStorageService, + @IOpenerService private readonly openerService: IOpenerService, @INotificationService private readonly notificationService: INotificationService, - @IHostService hostService: IHostService, + @IHostService private readonly hostService: IHostService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IExperimentService private readonly experimentService: IExperimentService, @IConfigurationService private readonly configurationService: IConfigurationService, @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, - @IProductService productService: IProductService - ) { - if (!productService.telemetryOptOutUrl || storageService.get(AbstractTelemetryOptOut.TELEMETRY_OPT_OUT_SHOWN, StorageScope.GLOBAL)) { - return; - } - const experimentId = 'telemetryOptOut'; - Promise.all([ - this.getWindowCount(), - experimentService.getExperimentById(experimentId) - ]).then(([count, experimentState]) => { - if (!hostService.hasFocus && count > 1) { - return; + @IProductService private readonly productService: IProductService, + ) { } + + protected async handleTelemetryOptOut(): Promise { + if (this.productService.telemetryOptOutUrl && !this.storageService.get(AbstractTelemetryOptOut.TELEMETRY_OPT_OUT_SHOWN, StorageScope.GLOBAL)) { + const experimentId = 'telemetryOptOut'; + + const [count, experimentState] = await Promise.all([this.getWindowCount(), this.experimentService.getExperimentById(experimentId)]); + + if (!this.hostService.hasFocus && count > 1) { + return; // return early if meanwhile another window opened (we only show the opt-out once) } - storageService.store(AbstractTelemetryOptOut.TELEMETRY_OPT_OUT_SHOWN, true, StorageScope.GLOBAL); - this.privacyUrl = productService.privacyStatementUrl || productService.telemetryOptOutUrl; + this.storageService.store(AbstractTelemetryOptOut.TELEMETRY_OPT_OUT_SHOWN, true, StorageScope.GLOBAL); - if (experimentState && experimentState.state === ExperimentState.Run && telemetryService.isOptedIn) { + this.privacyUrl = this.productService.privacyStatementUrl || this.productService.telemetryOptOutUrl; + + if (experimentState && experimentState.state === ExperimentState.Run && this.telemetryService.isOptedIn) { this.runExperiment(experimentId); return; } - const telemetryOptOutUrl = productService.telemetryOptOutUrl; + const telemetryOptOutUrl = this.productService.telemetryOptOutUrl; if (telemetryOptOutUrl) { - const optOutNotice = localize('telemetryOptOut.optOutNotice', "Help improve Azure Data Studio by allowing Microsoft to collect usage data. Read our [privacy statement]({0}) and learn how to [opt out]({1}).", this.privacyUrl, productService.telemetryOptOutUrl); // {{SQL CARBON EDIT}} VScode to ads - const optInNotice = localize('telemetryOptOut.optInNotice', "Help improve Azure Data Studio by allowing Microsoft to collect usage data. Read our [privacy statement]({0}) and learn how to [opt in]({1}).", this.privacyUrl, productService.telemetryOptOutUrl); // {{SQL CARBON EDIT}} VScode to ads - - notificationService.prompt( - Severity.Info, - telemetryService.isOptedIn ? optOutNotice : optInNotice, - [{ - label: localize('telemetryOptOut.readMore', "Read More"), - run: () => openerService.open(URI.parse(telemetryOptOutUrl)) - }], - { sticky: true } - ); + this.showTelemetryOptOut(telemetryOptOutUrl); } - }) - .then(undefined, onUnexpectedError); + } + } + + private showTelemetryOptOut(telemetryOptOutUrl: string): void { + const optOutNotice = localize('telemetryOptOut.optOutNotice', "Help improve VS Code by allowing Microsoft to collect usage data. Read our [privacy statement]({0}) and learn how to [opt out]({1}).", this.privacyUrl, this.productService.telemetryOptOutUrl); + const optInNotice = localize('telemetryOptOut.optInNotice', "Help improve VS Code by allowing Microsoft to collect usage data. Read our [privacy statement]({0}) and learn how to [opt in]({1}).", this.privacyUrl, this.productService.telemetryOptOutUrl); + + this.notificationService.prompt( + Severity.Info, + this.telemetryService.isOptedIn ? optOutNotice : optInNotice, + [{ + label: localize('telemetryOptOut.readMore', "Read More"), + run: () => this.openerService.open(URI.parse(telemetryOptOutUrl)) + }], + { sticky: true } + ); } protected abstract getWindowCount(): Promise; @@ -98,7 +100,7 @@ export abstract class AbstractTelemetryOptOut implements IWorkbenchContribution return this.galleryService.getCoreTranslation(extensionToFetchTranslationsFrom, locale!) .then(translation => { - const translationsFromPack: any = translation && translation.contents ? translation.contents['vs/workbench/contrib/welcome/gettingStarted/electron-browser/telemetryOptOut'] : {}; + const translationsFromPack: any = translation && translation.contents ? translation.contents['vs/workbench/contrib/welcome/telemetryOptOut/electron-browser/telemetryOptOut'] : {}; if (!!translationsFromPack[promptMessageKey] && !!translationsFromPack[yesLabelKey] && !!translationsFromPack[noLabelKey]) { promptMessage = translationsFromPack[promptMessageKey].replace('{0}', this.privacyUrl) + ' (Please help Microsoft improve Visual Studio Code by allowing the collection of usage data.)'; yesLabel = translationsFromPack[yesLabelKey] + ' (Yes)'; @@ -153,6 +155,22 @@ export abstract class AbstractTelemetryOptOut implements IWorkbenchContribution export class BrowserTelemetryOptOut extends AbstractTelemetryOptOut { + constructor( + @IStorageService storageService: IStorageService, + @IOpenerService openerService: IOpenerService, + @INotificationService notificationService: INotificationService, + @IHostService hostService: IHostService, + @ITelemetryService telemetryService: ITelemetryService, + @IExperimentService experimentService: IExperimentService, + @IConfigurationService configurationService: IConfigurationService, + @IExtensionGalleryService galleryService: IExtensionGalleryService, + @IProductService productService: IProductService + ) { + super(storageService, openerService, notificationService, hostService, telemetryService, experimentService, configurationService, galleryService, productService); + + this.handleTelemetryOptOut(); + } + protected async getWindowCount(): Promise { return 1; } diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.contribution.ts b/src/vs/workbench/contrib/welcome/telemetryOptOut/electron-browser/telemetryOptOut.contribution.ts similarity index 71% rename from src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.contribution.ts rename to src/vs/workbench/contrib/welcome/telemetryOptOut/electron-browser/telemetryOptOut.contribution.ts index e7d562aca3..ee753686bb 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.contribution.ts +++ b/src/vs/workbench/contrib/welcome/telemetryOptOut/electron-browser/telemetryOptOut.contribution.ts @@ -6,8 +6,6 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { NativeTelemetryOptOut } from 'vs/workbench/contrib/welcome/gettingStarted/electron-browser/telemetryOptOut'; -import { OpenWelcomePageInBrowser } from 'vs/workbench/contrib/welcome/gettingStarted/electron-browser/openWebsite'; +import { NativeTelemetryOptOut } from 'vs/workbench/contrib/welcome/telemetryOptOut/electron-browser/telemetryOptOut'; -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(OpenWelcomePageInBrowser, LifecyclePhase.Restored); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(NativeTelemetryOptOut, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/telemetryOptOut.ts b/src/vs/workbench/contrib/welcome/telemetryOptOut/electron-browser/telemetryOptOut.ts similarity index 96% rename from src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/telemetryOptOut.ts rename to src/vs/workbench/contrib/welcome/telemetryOptOut/electron-browser/telemetryOptOut.ts index c9a32866ce..bed589c92f 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/telemetryOptOut.ts +++ b/src/vs/workbench/contrib/welcome/telemetryOptOut/electron-browser/telemetryOptOut.ts @@ -12,7 +12,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IProductService } from 'vs/platform/product/common/productService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { AbstractTelemetryOptOut } from 'vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut'; +import { AbstractTelemetryOptOut } from 'vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut'; import { IElectronService } from 'vs/platform/electron/node/electron'; export class NativeTelemetryOptOut extends AbstractTelemetryOptOut { @@ -30,6 +30,8 @@ export class NativeTelemetryOptOut extends AbstractTelemetryOptOut { @IElectronService private readonly electronService: IElectronService ) { super(storageService, openerService, notificationService, hostService, telemetryService, experimentService, configurationService, galleryService, productService); + + this.handleTelemetryOptOut(); } protected getWindowCount(): Promise { diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.ts index b791a8bf95..e52ea95e87 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.ts @@ -160,7 +160,7 @@ Emmet takes the snippets idea to a whole new level: you can type CSS-like expres ul>li.item$*5 ||| ->**Tip:** The [Emmet cheat sheet](http://docs.emmet.io/cheat-sheet/) is a great source of Emmet syntax suggestions. To expand Emmet abbreviations and snippets using the |tab| key use the |emmet.triggerExpansionOnTab| [setting](command:workbench.action.openGlobalSettings). Check out the docs on [Emmet in VS Code](https://code.visualstudio.com/docs/editor/emmet) to learn more. +>**Tip:** The [Emmet cheat sheet](https://docs.emmet.io/cheat-sheet/) is a great source of Emmet syntax suggestions. To expand Emmet abbreviations and snippets using the |tab| key use the |emmet.triggerExpansionOnTab| [setting](command:workbench.action.openGlobalSettings). Check out the docs on [Emmet in VS Code](https://code.visualstudio.com/docs/editor/emmet) to learn more. diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts index 1f558f35ea..2ca566f8d1 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts @@ -10,6 +10,7 @@ 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'; +import { isEqual } from 'vs/base/common/resources'; export class WalkThroughModel extends EditorModel { @@ -78,7 +79,7 @@ export class WalkThroughInput extends EditorInput { return this.options.telemetryFrom; } - getTelemetryDescriptor(): { [key: string]: unknown } { + getTelemetryDescriptor(): { [key: string]: unknown; } { const descriptor = super.getTelemetryDescriptor(); descriptor['target'] = this.getTelemetryFrom(); /* __GDPR__FRAGMENT__ @@ -130,7 +131,7 @@ export class WalkThroughInput extends EditorInput { let otherResourceEditorInput = otherInput; // Compare by properties - return otherResourceEditorInput.options.resource.toString() === this.options.resource.toString(); + return isEqual(otherResourceEditorInput.options.resource, this.options.resource); } return false; diff --git a/src/vs/workbench/electron-browser/actions/developerActions.ts b/src/vs/workbench/electron-browser/actions/developerActions.ts index 8a91a1f085..9b23f3342b 100644 --- a/src/vs/workbench/electron-browser/actions/developerActions.ts +++ b/src/vs/workbench/electron-browser/actions/developerActions.ts @@ -7,13 +7,19 @@ import { Action } from 'vs/base/common/actions'; import * as nls from 'vs/nls'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class ToggleDevToolsAction extends Action { static readonly ID = 'workbench.action.toggleDevTools'; - static LABEL = nls.localize('toggleDevTools', "Toggle Developer Tools"); + static readonly LABEL = nls.localize('toggleDevTools', "Toggle Developer Tools"); - constructor(id: string, label: string, @IElectronService private readonly electronService: IElectronService) { + constructor( + id: string, + label: string, + @IElectronService private readonly electronService: IElectronService + ) { super(id, label); } @@ -25,9 +31,13 @@ export class ToggleDevToolsAction extends Action { export class ToggleSharedProcessAction extends Action { static readonly ID = 'workbench.action.toggleSharedProcess'; - static LABEL = nls.localize('toggleSharedProcess', "Toggle Shared Process"); + static readonly LABEL = nls.localize('toggleSharedProcess', "Toggle Shared Process"); - constructor(id: string, label: string, @ISharedProcessService private readonly sharedProcessService: ISharedProcessService) { + constructor( + id: string, + label: string, + @ISharedProcessService private readonly sharedProcessService: ISharedProcessService + ) { super(id, label); } @@ -35,3 +45,22 @@ export class ToggleSharedProcessAction extends Action { return this.sharedProcessService.toggleSharedProcessWindow(); } } + +export class ConfigureRuntimeArgumentsAction extends Action { + + static readonly ID = 'workbench.action.configureRuntimeArguments'; + static readonly LABEL = nls.localize('configureRuntimeArguments', "Configure Runtime Arguments"); + + constructor( + id: string, + label: string, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IEditorService private readonly editorService: IEditorService + ) { + super(id, label); + } + + async run(): Promise { + await this.editorService.openEditor({ resource: this.environmentService.argvResource }); + } +} diff --git a/src/vs/workbench/electron-browser/actions/windowActions.ts b/src/vs/workbench/electron-browser/actions/windowActions.ts index 1c0799fca9..b7b261525c 100644 --- a/src/vs/workbench/electron-browser/actions/windowActions.ts +++ b/src/vs/workbench/electron-browser/actions/windowActions.ts @@ -137,10 +137,10 @@ export class ZoomResetAction extends BaseZoomAction { } } -export class RestartWithExtensionsDisabledAction extends Action { +export class ReloadWindowWithExtensionsDisabledAction extends Action { - static readonly ID = 'workbench.action.restartWithExtensionsDisabled'; - static LABEL = nls.localize('restartWithExtensionsDisabled', "Restart With Extensions Disabled"); + static readonly ID = 'workbench.action.reloadWindowWithExtensionsDisabled'; + static readonly LABEL = nls.localize('reloadWindowWithExtensionsDisabled', "Reload With Extensions Disabled"); constructor( id: string, @@ -151,7 +151,7 @@ export class RestartWithExtensionsDisabledAction extends Action { } async run(): Promise { - await this.electronService.relaunch({ addArgs: ['--disable-extensions'] }); + await this.electronService.reload({ disableExtensions: true }); return true; } @@ -217,7 +217,7 @@ export abstract class BaseSwitchWindow extends Action { export class SwitchWindow extends BaseSwitchWindow { static readonly ID = 'workbench.action.switchWindow'; - static LABEL = nls.localize('switchWindow', "Switch Window..."); + static readonly LABEL = nls.localize('switchWindow', "Switch Window..."); constructor( id: string, @@ -240,7 +240,7 @@ export class SwitchWindow extends BaseSwitchWindow { export class QuickSwitchWindow extends BaseSwitchWindow { static readonly ID = 'workbench.action.quickSwitchWindow'; - static LABEL = nls.localize('quickSwitchWindow', "Quick Switch Window..."); + static readonly LABEL = nls.localize('quickSwitchWindow', "Quick Switch Window..."); constructor( id: string, diff --git a/src/vs/workbench/electron-browser/actions/workspaceActions.ts b/src/vs/workbench/electron-browser/actions/workspaceActions.ts index 471e3cd55d..eb26b3b4c9 100644 --- a/src/vs/workbench/electron-browser/actions/workspaceActions.ts +++ b/src/vs/workbench/electron-browser/actions/workspaceActions.ts @@ -14,7 +14,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ export class SaveWorkspaceAsAction extends Action { static readonly ID = 'workbench.action.saveWorkspaceAs'; - static LABEL = nls.localize('saveWorkspaceAsAction', "Save Workspace As..."); + static readonly LABEL = nls.localize('saveWorkspaceAsAction', "Save Workspace As..."); constructor( id: string, diff --git a/src/vs/workbench/electron-browser/desktop.contribution.ts b/src/vs/workbench/electron-browser/desktop.contribution.ts index 324554cedf..936f946794 100644 --- a/src/vs/workbench/electron-browser/desktop.contribution.ts +++ b/src/vs/workbench/electron-browser/desktop.contribution.ts @@ -11,8 +11,8 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; -import { ToggleSharedProcessAction, ToggleDevToolsAction } from 'vs/workbench/electron-browser/actions/developerActions'; -import { ZoomResetAction, ZoomOutAction, ZoomInAction, CloseCurrentWindowAction, SwitchWindow, QuickSwitchWindow, RestartWithExtensionsDisabledAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler } from 'vs/workbench/electron-browser/actions/windowActions'; +import { ToggleSharedProcessAction, ToggleDevToolsAction, ConfigureRuntimeArgumentsAction } from 'vs/workbench/electron-browser/actions/developerActions'; +import { ZoomResetAction, ZoomOutAction, ZoomInAction, CloseCurrentWindowAction, SwitchWindow, QuickSwitchWindow, ReloadWindowWithExtensionsDisabledAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler } from 'vs/workbench/electron-browser/actions/windowActions'; import { SaveWorkspaceAsAction, DuplicateWorkspaceInNewWindowAction } from 'vs/workbench/electron-browser/actions/workspaceActions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -21,6 +21,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { SupportsWorkspacesContext, IsMacContext, HasMacNativeTabsContext, IsDevelopmentContext } from 'vs/workbench/browser/contextkeys'; import { NoEditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/common/editor'; import { IElectronService } from 'vs/platform/electron/node/electron'; +import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; // {{SQL CARBON EDIT}} add import @@ -100,7 +101,8 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten (function registerDeveloperActions(): void { const developerCategory = nls.localize('developer', "Developer"); registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSharedProcessAction, ToggleSharedProcessAction.ID, ToggleSharedProcessAction.LABEL), 'Developer: Toggle Shared Process', developerCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(RestartWithExtensionsDisabledAction, RestartWithExtensionsDisabledAction.ID, RestartWithExtensionsDisabledAction.LABEL), 'Developer: Restart With Extensions Disabled', developerCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureRuntimeArgumentsAction, ConfigureRuntimeArgumentsAction.ID, ConfigureRuntimeArgumentsAction.LABEL), 'Developer: Configure Runtime Arguments', developerCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(ReloadWindowWithExtensionsDisabledAction, ReloadWindowWithExtensionsDisabledAction.ID, ReloadWindowWithExtensionsDisabledAction.LABEL), 'Developer: Reload With Extensions Disabled', developerCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleDevToolsAction, ToggleDevToolsAction.ID, ToggleDevToolsAction.LABEL), 'Developer: Toggle Developer Tools', developerCategory); KeybindingsRegistry.registerKeybindingRule({ @@ -317,7 +319,7 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten 'default': false, 'scope': ConfigurationScope.APPLICATION, 'description': nls.localize('window.nativeTabs', "Enables macOS Sierra window tabs. Note that changes require a full restart to apply and that native tabs will disable a custom title bar style if configured."), - 'included': isMacintosh && parseFloat(os.release()) >= 16 // Minimum: macOS Sierra (10.12.x = darwin 16.x) + 'included': isMacintosh && parseFloat(os.release()) >= 17 // Minimum: macOS Sierra (10.13.x = darwin 17.x) }, 'window.nativeFullScreen': { 'type': 'boolean', @@ -352,3 +354,32 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten } }); })(); + +// JSON Schemas +(function registerJSONSchemas(): void { + const argvDefinitionFileSchemaId = 'vscode://schemas/argv'; + const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); + + jsonRegistry.registerSchema(argvDefinitionFileSchemaId, { + id: argvDefinitionFileSchemaId, + allowComments: true, + allowTrailingCommas: true, + description: 'VSCode static command line definition file', + type: 'object', + additionalProperties: false, + properties: { + locale: { + type: 'string', + description: nls.localize('argv.locale', 'The display Language to use. Picking a different language requires the associated language pack to be installed.') + }, + 'disable-hardware-acceleration': { + type: 'boolean', + description: nls.localize('argv.disableHardwareAcceleration', 'Disables hardware acceleration. ONLY change this option if you encounter graphic issues.') + }, + 'disable-color-correct-rendering': { + type: 'boolean', + description: nls.localize('argv.disableColorCorrectRendering', 'Resolves issues around color profile selection. ONLY change this option if you encounter graphic issues.') + } + } + }); +})(); diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index 3f97dbe551..e03cb33c74 100644 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -96,7 +96,7 @@ class DesktopMain extends Disposable { } const filesToWait = this.environmentService.configuration.filesToWait; - const filesToWaitPaths = filesToWait && filesToWait.paths; + const filesToWaitPaths = filesToWait?.paths; [filesToWaitPaths, this.environmentService.configuration.filesToOpenOrCreate, this.environmentService.configuration.filesToDiff].forEach(paths => { if (Array.isArray(paths)) { paths.forEach(path => { diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 83600062ec..d72cf86bc6 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -50,7 +50,7 @@ import { IUpdateService } from 'vs/platform/update/common/update'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IPreferencesService } from '../services/preferences/common/preferences'; import { IMenubarService, IMenubarData, IMenubarMenu, IMenubarKeybinding, IMenubarMenuItemSubmenu, IMenubarMenuItemAction, MenubarMenuItem } from 'vs/platform/menubar/node/menubar'; -import { withNullAsUndefined } from 'vs/base/common/types'; +import { withNullAsUndefined, assertIsDefined } from 'vs/base/common/types'; import { IOpenerService, OpenOptions } from 'vs/platform/opener/common/opener'; import { Schemas } from 'vs/base/common/network'; import { IElectronService } from 'vs/platform/electron/node/electron'; @@ -192,7 +192,7 @@ export class ElectronWindow extends Disposable { // High Contrast Events ipc.on('vscode:enterHighContrast', async () => { const windowConfig = this.configurationService.getValue('window'); - if (windowConfig && windowConfig.autoDetectHighContrast) { + if (windowConfig?.autoDetectHighContrast) { await this.lifecycleService.when(LifecyclePhase.Ready); this.themeService.setColorTheme(VS_HC_THEME, undefined); } @@ -200,7 +200,7 @@ export class ElectronWindow extends Disposable { ipc.on('vscode:leaveHighContrast', async () => { const windowConfig = this.configurationService.getValue('window'); - if (windowConfig && windowConfig.autoDetectHighContrast) { + if (windowConfig?.autoDetectHighContrast) { await this.lifecycleService.when(LifecyclePhase.Ready); this.themeService.restoreColorTheme(); } @@ -253,7 +253,7 @@ export class ElectronWindow extends Disposable { // Maximize/Restore on doubleclick (for macOS custom title) if (isMacintosh && getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { - const titlePart = this.layoutService.getContainer(Parts.TITLEBAR_PART); + const titlePart = assertIsDefined(this.layoutService.getContainer(Parts.TITLEBAR_PART)); this._register(DOM.addDisposableListener(titlePart, DOM.EventType.DBLCLICK, e => { DOM.EventHelper.stop(e); @@ -423,7 +423,7 @@ export class ElectronWindow extends Disposable { this.openerService.registerExternalUriResolver({ resolveExternalUri: async (uri: URI, options?: OpenOptions) => { - if (options && options.allowTunneling) { + if (options?.allowTunneling) { const portMappingRequest = extractLocalHostUriMetaDataForPortMapping(uri); if (portMappingRequest) { const tunnel = await this.tunnelService.openTunnel(portMappingRequest.port); @@ -443,7 +443,7 @@ export class ElectronWindow extends Disposable { private shouldOpenExternal(resource: URI, options?: OpenOptions) { const scheme = resource.scheme.toLowerCase(); const preferOpenExternal = (scheme === Schemas.mailto || scheme === Schemas.http || scheme === Schemas.https); - return (options && options.openExternal) || preferOpenExternal; + return options?.openExternal || preferOpenExternal; } private updateTouchbarMenu(): void { @@ -632,8 +632,8 @@ export class ElectronWindow extends Disposable { await this.lifecycleService.when(LifecyclePhase.Ready); // In diffMode we open 2 resources as diff - if (diffMode && resources.length === 2) { - return this.editorService.openEditor({ leftResource: resources[0].resource!, rightResource: resources[1].resource!, options: { pinned: true } }); + if (diffMode && resources.length === 2 && resources[0].resource && resources[1].resource) { + return this.editorService.openEditor({ leftResource: resources[0].resource, rightResource: resources[1].resource, options: { pinned: true } }); } // For one file, just put it into the current active editor @@ -702,6 +702,11 @@ class NativeMenubarControl extends MenubarControl { } protected doUpdateMenubar(firstTime: boolean): void { + // Since the native menubar is shared between windows (main process) + // only allow the focused window to update the menubar + if (!this.hostService.hasFocus) { + return; + } // Send menus to main process to be rendered by Electron const menubarData = { menus: {}, keybindings: {} }; @@ -742,8 +747,8 @@ class NativeMenubarControl extends MenubarControl { 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 menu = this.menus[menuItem.item.submenu] = this.menuService.createMenu(menuItem.item.submenu, this.contextKeyService); + this._register(menu.onDidChange(() => this.updateMenubar())); } const menuToDispose = this.menuService.createMenu(menuItem.item.submenu, this.contextKeyService); diff --git a/src/vs/workbench/services/accessibility/node/accessibilityService.ts b/src/vs/workbench/services/accessibility/node/accessibilityService.ts index f141ab6d0e..2d3b3fba1e 100644 --- a/src/vs/workbench/services/accessibility/node/accessibilityService.ts +++ b/src/vs/workbench/services/accessibility/node/accessibilityService.ts @@ -10,6 +10,14 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { AbstractAccessibilityService } from 'vs/platform/accessibility/common/abstractAccessibilityService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; + +interface AccessibilityMetrics { + enabled: boolean; +} +type AccessibilityMetricsClassification = { + enabled: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; +}; export class AccessibilityService extends AbstractAccessibilityService implements IAccessibilityService { @@ -20,7 +28,8 @@ export class AccessibilityService extends AbstractAccessibilityService implement constructor( @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IContextKeyService readonly contextKeyService: IContextKeyService, - @IConfigurationService readonly configurationService: IConfigurationService + @IConfigurationService readonly configurationService: IConfigurationService, + @ITelemetryService private readonly _telemetryService: ITelemetryService ) { super(contextKeyService, configurationService); } @@ -51,12 +60,16 @@ export class AccessibilityService extends AbstractAccessibilityService implement this._accessibilitySupport = accessibilitySupport; this._onDidChangeAccessibilitySupport.fire(); + + if (accessibilitySupport === AccessibilitySupport.Enabled) { + this._telemetryService.publicLog2('accessibility', { enabled: true }); + } } getAccessibilitySupport(): AccessibilitySupport { if (this._accessibilitySupport === AccessibilitySupport.Unknown) { const config = this.environmentService.configuration; - this._accessibilitySupport = (config && config.accessibilitySupport) ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled; + this._accessibilitySupport = config?.accessibilitySupport ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled; } return this._accessibilitySupport; diff --git a/src/vs/workbench/services/clipboard/electron-browser/clipboardService.ts b/src/vs/workbench/services/clipboard/electron-browser/clipboardService.ts index 1421f539a1..61e752f16a 100644 --- a/src/vs/workbench/services/clipboard/electron-browser/clipboardService.ts +++ b/src/vs/workbench/services/clipboard/electron-browser/clipboardService.ts @@ -11,7 +11,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class NativeClipboardService implements IClipboardService { - private static FILE_FORMAT = 'code/file-list'; // Clipboard format for files + private static readonly FILE_FORMAT = 'code/file-list'; // Clipboard format for files _serviceBrand: undefined; diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 1c9ab98412..9c0dff877a 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -465,7 +465,7 @@ class FileServiceBasedWorkspaceConfiguration extends Disposable implements IWork } private consolidate(): void { - this.workspaceSettings = this.workspaceConfigurationModelParser.settingsModel.merge(this.workspaceConfigurationModelParser.launchModel); + this.workspaceSettings = this.workspaceConfigurationModelParser.settingsModel.merge(this.workspaceConfigurationModelParser.launchModel, this.workspaceConfigurationModelParser.tasksModel); } private watchWorkspaceConfigurationFile(): IDisposable { @@ -509,7 +509,7 @@ class CachedWorkspaceConfiguration extends Disposable implements IWorkspaceConfi const contents = await this.configurationCache.read(key); this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(key.key); this.workspaceConfigurationModelParser.parseContent(contents); - this.workspaceSettings = this.workspaceConfigurationModelParser.settingsModel.merge(this.workspaceConfigurationModelParser.launchModel); + this.workspaceSettings = this.workspaceConfigurationModelParser.settingsModel.merge(this.workspaceConfigurationModelParser.launchModel, this.workspaceConfigurationModelParser.tasksModel); } catch (e) { } } diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index bd23d55cc8..e133b052db 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -59,10 +59,10 @@ export class WorkspaceService extends Disposable implements IConfigurationServic protected readonly _onDidChangeWorkbenchState: Emitter = this._register(new Emitter()); public readonly onDidChangeWorkbenchState: Event = this._onDidChangeWorkbenchState.event; - private configurationEditingService: ConfigurationEditingService; - private jsonEditingService: JSONEditingService; - - private cyclicDependencyReady: Function; + // TODO@sandeep debt with cyclic dependencies + private configurationEditingService!: ConfigurationEditingService; + private jsonEditingService!: JSONEditingService; + private cyclicDependencyReady!: Function; private cyclicDependency = new Promise(resolve => this.cyclicDependencyReady = resolve); constructor( diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index db2853ceda..41281c2513 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -428,11 +428,6 @@ export class ConfigurationEditingService { if (target === EditableConfigurationTarget.USER_LOCAL || target === EditableConfigurationTarget.USER_REMOTE) { return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_USER_TARGET, target, operation); } - - // Workspace tasks are not supported - if (operation.workspaceStandAloneConfigurationKey === TASKS_CONFIGURATION_KEY && this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE && operation.target === EditableConfigurationTarget.WORKSPACE) { - return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_TARGET, target, operation); - } } // Target cannot be workspace or folder if no workspace opened diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index fc07e7a648..8a5ff31ac4 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -17,11 +17,13 @@ export class WorkspaceConfigurationModelParser extends ConfigurationModelParser private _folders: IStoredWorkspaceFolder[] = []; private _settingsModelParser: ConfigurationModelParser; private _launchModel: ConfigurationModel; + private _tasksModel: ConfigurationModel; constructor(name: string) { super(name); this._settingsModelParser = new ConfigurationModelParser(name, WORKSPACE_SCOPES); this._launchModel = new ConfigurationModel(); + this._tasksModel = new ConfigurationModel(); } get folders(): IStoredWorkspaceFolder[] { @@ -36,6 +38,10 @@ export class WorkspaceConfigurationModelParser extends ConfigurationModelParser return this._launchModel; } + get tasksModel(): ConfigurationModel { + return this._tasksModel; + } + reprocessWorkspaceSettings(): void { this._settingsModelParser.parse(); } @@ -44,6 +50,7 @@ export class WorkspaceConfigurationModelParser extends ConfigurationModelParser this._folders = (raw['folders'] || []) as IStoredWorkspaceFolder[]; this._settingsModelParser.parseRaw(raw['settings']); this._launchModel = this.createConfigurationModelFrom(raw, 'launch'); + this._tasksModel = this.createConfigurationModelFrom(raw, 'tasks'); return super.doParseRaw(raw); } 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 c69236a91d..3cf078487c 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 @@ -185,7 +185,7 @@ suite('ConfigurationEditingService', () => { test('do not notify error', () => { instantiationService.stub(ITextFileService, 'isDirty', true); const target = sinon.stub(); - instantiationService.stub(INotificationService, { prompt: target, _serviceBrand: undefined, notify: null!, error: null!, info: null!, warn: null!, status: null! }); + instantiationService.stub(INotificationService, { prompt: target, _serviceBrand: undefined, notify: null!, error: null!, info: null!, warn: null!, status: null!, setFilter: 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 8aa22e6f88..7528c24e9b 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 @@ -1406,6 +1406,53 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED }); }); + + test('get tasks configuration', () => { + const expectedTasksConfiguration = { + 'version': '2.0.0', + 'tasks': [ + { + 'label': 'Run Dev', + 'type': 'shell', + 'command': './scripts/code.sh', + 'windows': { + 'command': '.\\scripts\\code.bat' + }, + 'problemMatcher': [] + } + ] + }; + return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'tasks', value: expectedTasksConfiguration }], true) + .then(() => testObject.reloadConfiguration()) + .then(() => { + const actual = testObject.getValue('tasks'); + assert.deepEqual(actual, expectedTasksConfiguration); + }); + }); + + test('inspect tasks configuration', () => { + const expectedTasksConfiguration = { + 'version': '2.0.0', + 'tasks': [ + { + 'label': 'Run Dev', + 'type': 'shell', + 'command': './scripts/code.sh', + 'windows': { + 'command': '.\\scripts\\code.bat' + }, + 'problemMatcher': [] + } + ] + }; + return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'tasks', value: expectedTasksConfiguration }], true) + .then(() => testObject.reloadConfiguration()) + .then(() => { + const actual = testObject.inspect('tasks').workspace; + assert.deepEqual(actual, expectedTasksConfiguration); + }); + }); + test('update user configuration', () => { return testObject.updateValue('configurationService.workspace.testSetting', 'userValue', ConfigurationTarget.USER) .then(() => assert.equal(testObject.getValue('configurationService.workspace.testSetting'), 'userValue')); @@ -1483,25 +1530,17 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED .then(() => assert.deepEqual(testObject.getValue('tasks', { resource: workspace.folders[0].uri }), { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] })); }); - test('update tasks configuration in a workspace is not supported', () => { - const workspace = workspaceContextService.getWorkspace(); - return testObject.updateValue('tasks', { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] }, { resource: workspace.folders[0].uri }, ConfigurationTarget.WORKSPACE, true) - .then(() => assert.fail('Should not be supported'), (e) => assert.equal(e.code, ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_TARGET)); - }); - test('update launch configuration in a workspace', () => { const workspace = workspaceContextService.getWorkspace(); return testObject.updateValue('launch', { 'version': '1.0.0', configurations: [{ 'name': 'myLaunch' }] }, { resource: workspace.folders[0].uri }, ConfigurationTarget.WORKSPACE, true) .then(() => assert.deepEqual(testObject.getValue('launch'), { 'version': '1.0.0', configurations: [{ 'name': 'myLaunch' }] })); }); - test('task configurations are not read from workspace', () => { - return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'tasks', value: { 'version': '1.0' } }], true) - .then(() => testObject.reloadConfiguration()) - .then(() => { - const actual = testObject.inspect('tasks.version'); - assert.equal(actual.workspace, undefined); - }); + test('update tasks configuration in a workspace', () => { + const workspace = workspaceContextService.getWorkspace(); + const tasks = { 'version': '2.0.0', tasks: [{ 'label': 'myTask' }] }; + return testObject.updateValue('tasks', tasks, { resource: workspace.folders[0].uri }, ConfigurationTarget.WORKSPACE, true) + .then(() => assert.deepEqual(testObject.getValue('tasks'), tasks)); }); test('configuration of newly added folder is available on configuration change event', async () => { diff --git a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts index 2c9b197a27..a5e9655b63 100644 --- a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts @@ -25,7 +25,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export abstract class BaseConfigurationResolverService extends AbstractVariableResolverService { - static INPUT_OR_COMMAND_VARIABLES_PATTERN = /\${((input|command):(.*?))}/g; + static readonly INPUT_OR_COMMAND_VARIABLES_PATTERN = /\${((input|command):(.*?))}/g; constructor( envVariables: IProcessEnvironment, @@ -88,10 +88,10 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR public async resolveWithInteractionReplace(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary): Promise { // resolve any non-interactive variables and any contributed variables - config = await this.resolveAny(folder, config); + config = this.resolveAny(folder, config); // resolve input variables in the order in which they are encountered - return this.resolveWithInteraction(folder, config, section, variables, true).then(mapping => { + return this.resolveWithInteraction(folder, config, section, variables).then(mapping => { // finally substitute evaluated command variables (if there are any) if (!mapping) { return null; @@ -103,7 +103,7 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR }); } - public async resolveWithInteraction(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary, skipContributed: boolean = false): Promise | undefined> { + public async resolveWithInteraction(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary): Promise | undefined> { // resolve any non-interactive variables and any contributed variables const resolved = await this.resolveAnyMap(folder, config); config = resolved.newConfig; @@ -180,6 +180,11 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR throw new Error(nls.localize('commandVariable.noStringType', "Cannot substitute command variable '{0}' because command did not return a result of type string.", commandId)); } break; + default: + // Try to resolve it as a contributed variable + if (this._contributedVariables.has(variable)) { + result = await this._contributedVariables.get(variable)!(); + } } if (typeof result === 'string') { @@ -208,6 +213,11 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR } } } + this._contributedVariables.forEach((value, contributed: string) => { + if ((variables.indexOf(contributed) < 0) && (object.indexOf('${' + contributed + '}') >= 0)) { + variables.push(contributed); + } + }); } else if (Types.isArray(object)) { object.forEach(value => { this.findVariables(value, variables); diff --git a/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts b/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts index 7994a2bdff..da3758e3f3 100644 --- a/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts +++ b/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts @@ -20,7 +20,7 @@ export interface IConfigurationResolverService { * Recursively resolves all variables in the given config and returns a copy of it with substituted values. * Command variables are only substituted if a "commandValueMapping" dictionary is given and if it contains an entry for the command. */ - resolveAny(folder: IWorkspaceFolder | undefined, config: any, commandValueMapping?: IStringDictionary): Promise; + resolveAny(folder: IWorkspaceFolder | undefined, config: any, commandValueMapping?: IStringDictionary): any; /** * Recursively resolves all variables (including commands and user input) in the given config and returns a copy of it with substituted values. diff --git a/src/vs/workbench/services/configurationResolver/common/variableResolver.ts b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts index fa914ed8e3..0fef61e5c9 100644 --- a/src/vs/workbench/services/configurationResolver/common/variableResolver.ts +++ b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts @@ -27,12 +27,12 @@ export interface IVariableResolveContext { export class AbstractVariableResolverService implements IConfigurationResolverService { - static VARIABLE_REGEXP = /\$\{(.*?)\}/g; - static VARIABLE_REGEXP_SINGLE = /\$\{(.*?)\}/; + static readonly VARIABLE_REGEXP = /\$\{(.*?)\}/g; + static readonly VARIABLE_REGEXP_SINGLE = /\$\{(.*?)\}/; _serviceBrand: undefined; - private _contributedVariables: Map Promise> = new Map(); + protected _contributedVariables: Map Promise> = new Map(); constructor( private _context: IVariableResolveContext, @@ -53,7 +53,8 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe return this.recursiveResolve(root ? root.uri : undefined, value); } - private async resolveAnyBase(workspaceFolder: IWorkspaceFolder | undefined, config: any, commandValueMapping?: IStringDictionary, resolvedVariables?: Map): Promise { + public resolveAnyBase(workspaceFolder: IWorkspaceFolder | undefined, config: any, commandValueMapping?: IStringDictionary, resolvedVariables?: Map): any { + const result = objects.deepClone(config) as any; // hoist platform specific attributes to top level @@ -71,16 +72,16 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe delete result.linux; // substitute all variables recursively in string values - return this.recursiveResolvePromise(workspaceFolder ? workspaceFolder.uri : undefined, result, commandValueMapping, resolvedVariables); + return this.recursiveResolve(workspaceFolder ? workspaceFolder.uri : undefined, result, commandValueMapping, resolvedVariables); } - public resolveAny(workspaceFolder: IWorkspaceFolder | undefined, config: any, commandValueMapping?: IStringDictionary): Promise { + public resolveAny(workspaceFolder: IWorkspaceFolder | undefined, config: any, commandValueMapping?: IStringDictionary): any { return this.resolveAnyBase(workspaceFolder, config, commandValueMapping); } - protected async resolveAnyMap(workspaceFolder: IWorkspaceFolder | undefined, config: any, commandValueMapping?: IStringDictionary): Promise<{ newConfig: any, resolvedVariables: Map }> { + public resolveAnyMap(workspaceFolder: IWorkspaceFolder | undefined, config: any, commandValueMapping?: IStringDictionary): { newConfig: any, resolvedVariables: Map } { const resolvedVariables = new Map(); - const newConfig = await this.resolveAnyBase(workspaceFolder, config, commandValueMapping, resolvedVariables); + const newConfig = this.resolveAnyBase(workspaceFolder, config, commandValueMapping, resolvedVariables); return { newConfig, resolvedVariables }; } @@ -116,23 +117,6 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe return value; } - private async recursiveResolvePromise(folderUri: uri | undefined, value: any, commandValueMapping?: IStringDictionary, resolvedVariables?: Map): Promise { - if (types.isString(value)) { - return this.resolveStringPromise(folderUri, value, commandValueMapping, resolvedVariables); - } else if (types.isArray(value)) { - return Promise.all(value.map(s => this.recursiveResolvePromise(folderUri, s, commandValueMapping, resolvedVariables))); - } else if (types.isObject(value)) { - let result: IStringDictionary | string[]> = Object.create(null); - const keys = Object.keys(value); - for (let key of keys) { - const replaced = await this.resolveStringPromise(folderUri, key, commandValueMapping, resolvedVariables); - result[replaced] = await this.recursiveResolvePromise(folderUri, value[key], commandValueMapping, resolvedVariables); - } - return result; - } - return value; - } - private resolveString(folderUri: uri | undefined, value: string, commandValueMapping: IStringDictionary | undefined, resolvedVariables?: Map): string { // loop through all variables occurrences in 'value' @@ -150,37 +134,6 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe return replaced; } - private async resolveStringPromise(folderUri: uri | undefined, value: string, commandValueMapping: IStringDictionary | undefined, resolvedVariables?: Map): Promise { - // loop through all variables occurrences in 'value' - const matches = value.match(AbstractVariableResolverService.VARIABLE_REGEXP); - const replaces: Map = new Map(); - if (!matches) { - return value; - } - for (const match of matches) { - const evaluate = await this.evaluateSingleContributedVariable(match, match.match(AbstractVariableResolverService.VARIABLE_REGEXP_SINGLE)![1]); - if (evaluate !== match) { - replaces.set(match, evaluate); - } - } - - const replaced = value.replace(AbstractVariableResolverService.VARIABLE_REGEXP, (match: string, variable: string) => { - - let resolvedValue = this.evaluateSingleVariable(match, variable, folderUri, commandValueMapping); - if ((resolvedValue === match) && (replaces.has(match))) { - resolvedValue = replaces.get(match)!; - } - - if (resolvedVariables) { - resolvedVariables.set(variable, resolvedValue); - } - - return resolvedValue; - }); - - return replaced; - } - private evaluateSingleVariable(match: string, variable: string, folderUri: uri | undefined, commandValueMapping: IStringDictionary | undefined): string { // try to separate variable arguments from variable name @@ -323,25 +276,19 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe return match; default: - return match; + try { + return this.resolveFromMap(match, variable, commandValueMapping, undefined); + } catch (error) { + return match; + } } } } } - private async evaluateSingleContributedVariable(match: string, variable: string): Promise { - if (this._contributedVariables.has(variable)) { - const contributedValue: string | undefined = await this._contributedVariables.get(variable)!(); - if (contributedValue !== undefined) { - return contributedValue; - } - } - return match; - } - - private resolveFromMap(match: string, argument: string | undefined, commandValueMapping: IStringDictionary | undefined, prefix: string): string { + private resolveFromMap(match: string, argument: string | undefined, commandValueMapping: IStringDictionary | undefined, prefix: string | undefined): string { if (argument && commandValueMapping) { - const v = commandValueMapping[prefix + ':' + argument]; + const v = (prefix === undefined) ? commandValueMapping[argument] : commandValueMapping[prefix + ':' + argument]; if (typeof v === 'string') { return v; } diff --git a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts index da0a7a0692..86347e2a10 100644 --- a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts @@ -494,7 +494,7 @@ suite('Configuration Resolver Service', () => { 'name': '${' + variable + '}', }; configurationResolverService!.contributeVariable(variable, async () => { return buildTask; }); - return configurationResolverService!.resolveAny(workspace, configuration).then(result => { + return configurationResolverService!.resolveWithInteractionReplace(workspace, configuration).then(result => { assert.deepEqual(result, { 'name': `${buildTask}` }); diff --git a/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts index f00b8f6c79..20cc3c44d1 100644 --- a/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts @@ -138,10 +138,19 @@ class NativeContextMenuService extends Disposable implements IContextMenuService // Normal Menu Item else { + let type: 'radio' | 'checkbox' | undefined = undefined; + if (!!entry.checked) { + if (typeof delegate.getCheckedActionsRepresentation === 'function') { + type = delegate.getCheckedActionsRepresentation(entry); + } else { + type = 'checkbox'; + } + } + const item: IContextMenuItem = { label: unmnemonicLabel(entry.label), - checked: !!entry.checked || !!entry.radio, - type: !!entry.checked ? 'checkbox' : !!entry.radio ? 'radio' : undefined, + checked: !!entry.checked, + type, enabled: !!entry.enabled, click: event => { @@ -188,4 +197,4 @@ class NativeContextMenuService extends Disposable implements IContextMenuService } } -registerSingleton(IContextMenuService, ContextMenuService, true); \ No newline at end of file +registerSingleton(IContextMenuService, ContextMenuService, true); diff --git a/src/vs/workbench/services/credentials/browser/credentialsService.ts b/src/vs/workbench/services/credentials/browser/credentialsService.ts index 8303a976fa..bf6bc617c3 100644 --- a/src/vs/workbench/services/credentials/browser/credentialsService.ts +++ b/src/vs/workbench/services/credentials/browser/credentialsService.ts @@ -6,6 +6,7 @@ import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { find } from 'vs/base/common/arrays'; export interface ICredentialsProvider { getPassword(service: string, account: string): Promise; @@ -14,7 +15,7 @@ export interface ICredentialsProvider { deletePassword(service: string, account: string): Promise; findPassword(service: string): Promise; - findCredentials(service: string): Promise>; + findCredentials(service: string): Promise>; } export class BrowserCredentialsService implements ICredentialsService { @@ -47,7 +48,7 @@ export class BrowserCredentialsService implements ICredentialsService { return this.credentialsProvider.findPassword(service); } - findCredentials(service: string): Promise> { + findCredentials(service: string): Promise> { return this.credentialsProvider.findCredentials(service); } } @@ -88,17 +89,12 @@ class InMemoryCredentialsProvider implements ICredentialsProvider { return credential ? credential.password : null; } - private doFindPassword(service: string, account?: string): ICredential | null { - for (const credential of this.credentials) { - if (credential.service === service && (typeof account !== 'string' || credential.account === account)) { - return credential; - } - } - - return null; + private doFindPassword(service: string, account?: string): ICredential | undefined { + return find(this.credentials, credential => + credential.service === service && (typeof account !== 'string' || credential.account === account)); } - async findCredentials(service: string): Promise> { + async findCredentials(service: string): Promise> { return this.credentials .filter(credential => credential.service === service) .map(({ account, password }) => ({ account, password })); diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts index d8ef677c4b..7cf9130eb4 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 { Disposable, IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable, DisposableStore } 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'; @@ -19,6 +19,7 @@ import { localize } from 'vs/nls'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; class DecorationRule { @@ -78,7 +79,7 @@ class DecorationRule { // TODO @misolori update bubble badge to use class name instead of unicode createCSSRule( `.${this.bubbleBadgeClassName}::after`, - `content: "\uf052"; color: ${getColor(theme, color)}; font-family: octicons; font-size: 14px; padding-right: 10px; opacity: 0.4;`, + `content: "\uea71"; color: ${getColor(theme, color)}; font-family: codicon; font-size: 14px; padding-right: 14px; opacity: 0.4;`, element ); } @@ -96,25 +97,21 @@ class DecorationRule { } } -class DecorationStyles extends Disposable { +class DecorationStyles { private readonly _styleElement = createStyleSheet(); private readonly _decorationRules = new Map(); + private readonly _dispoables = new DisposableStore(); constructor( private _themeService: IThemeService, ) { - super(); - this._register(this._themeService.onThemeChange(this._onThemeChange, this)); + this._themeService.onThemeChange(this._onThemeChange, this, this._dispoables); } dispose(): void { - super.dispose(); - - const parent = this._styleElement.parentElement; - if (parent) { - parent.removeChild(this._styleElement); - } + this._dispoables.dispose(); + this._styleElement.remove(); } asDecoration(data: IDecorationData[], onlyChildren: boolean): IDecoration { @@ -172,7 +169,7 @@ class DecorationStyles extends Disposable { if (value.isUnused()) { let remove: boolean = false; if (Array.isArray(data)) { - remove = data.some(data => !usedDecorations.has(DecorationRule.keyOf(data))); + remove = data.every(data => !usedDecorations.has(DecorationRule.keyOf(data))); } else if (!usedDecorations.has(DecorationRule.keyOf(data))) { remove = true; } @@ -224,11 +221,11 @@ class DecorationProviderWrapper { private readonly _dispoable: IDisposable; constructor( - private readonly _provider: IDecorationsProvider, + readonly provider: IDecorationsProvider, private readonly _uriEmitter: Emitter, private readonly _flushEmitter: Emitter ) { - this._dispoable = this._provider.onDidChange(uris => { + this._dispoable = this.provider.onDidChange(uris => { if (!uris) { // flush event -> drop all data, can affect everything this.data.clear(); @@ -292,7 +289,7 @@ class DecorationProviderWrapper { } const source = new CancellationTokenSource(); - const dataOrThenable = this._provider.provideDecorations(uri, source.token); + const dataOrThenable = this.provider.provideDecorations(uri, source.token); if (!isThenable | undefined>(dataOrThenable)) { // sync -> we have a result now return this._keepItem(uri, dataOrThenable); @@ -325,7 +322,7 @@ class DecorationProviderWrapper { } } -export class FileDecorationsService implements IDecorationsService { +export class DecorationsService implements IDecorationsService { _serviceBrand: undefined; @@ -333,7 +330,6 @@ export class FileDecorationsService implements IDecorationsService { private readonly _onDidChangeDecorationsDelayed = new Emitter(); private readonly _onDidChangeDecorations = new Emitter(); private readonly _decorationStyles: DecorationStyles; - private readonly _disposables: IDisposable[]; readonly onDidChangeDecorations: Event = Event.any( this._onDidChangeDecorations.event, @@ -345,29 +341,25 @@ export class FileDecorationsService implements IDecorationsService { ); constructor( - @IThemeService themeService: IThemeService + @IThemeService themeService: IThemeService, + @ILogService private readonly _logService: ILogService, ) { this._decorationStyles = new DecorationStyles(themeService); // every so many events we check if there are // css styles that we don't need anymore let count = 0; - let reg = this.onDidChangeDecorations(() => { + this.onDidChangeDecorations(() => { if (++count % 17 === 0) { this._decorationStyles.cleanUp(this._data.iterator()); } }); - - this._disposables = [ - reg, - this._decorationStyles - ]; } dispose(): void { - dispose(this._disposables); - dispose(this._onDidChangeDecorations); - dispose(this._onDidChangeDecorationsDelayed); + this._decorationStyles.dispose(); + this._onDidChangeDecorations.dispose(); + this._onDidChangeDecorationsDelayed.dispose(); } registerDecorationsProvider(provider: IDecorationsProvider): IDisposable { @@ -397,10 +389,12 @@ export class FileDecorationsService implements IDecorationsService { let data: IDecorationData[] = []; let containsChildren: boolean = false; for (let iter = this._data.iterator(), next = iter.next(); !next.done; next = iter.next()) { + const { label } = next.value.provider; next.value.getOrRetrieve(uri, includeChildren, (deco, isChild) => { if (!isChild || deco.bubble) { data.push(deco); containsChildren = isChild || containsChildren; + this._logService.trace('DecorationsService#getDecoration#getOrRetrieve', label, deco, isChild, uri); } }); } @@ -420,4 +414,4 @@ function getColor(theme: ITheme, color: string | undefined) { return 'inherit'; } -registerSingleton(IDecorationsService, FileDecorationsService); +registerSingleton(IDecorationsService, DecorationsService, true); diff --git a/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts b/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts index 9890b41a6c..33402d630b 100644 --- a/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts +++ b/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts @@ -4,22 +4,23 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { FileDecorationsService } from 'vs/workbench/services/decorations/browser/decorationsService'; +import { DecorationsService } from 'vs/workbench/services/decorations/browser/decorationsService'; import { IDecorationsProvider, IDecorationData } from 'vs/workbench/services/decorations/browser/decorations'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { ConsoleLogService } from 'vs/platform/log/common/log'; suite('DecorationsService', function () { - let service: FileDecorationsService; + let service: DecorationsService; setup(function () { if (service) { service.dispose(); } - service = new FileDecorationsService(new TestThemeService()); + service = new DecorationsService(new TestThemeService(), new ConsoleLogService()); }); test('Async provider, async/evented result', function () { diff --git a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts index 1c1bb348b9..c6fdb83548 100644 --- a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts @@ -93,7 +93,7 @@ export abstract class AbstractFileDialogService { if (stat.isDirectory || options.forceNewWindow || preferNewWindow) { return this.hostService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow }); } else { - return this.openerService.open(uri); + return this.openerService.open(uri, { fromUserGesture: true }); } } } @@ -107,7 +107,7 @@ export abstract class AbstractFileDialogService { if (options.forceNewWindow || preferNewWindow) { return this.hostService.openWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow }); } else { - return this.openerService.open(uri); + return this.openerService.open(uri, { fromUserGesture: true }); } } } diff --git a/src/vs/workbench/services/dialogs/browser/dialogService.ts b/src/vs/workbench/services/dialogs/browser/dialogService.ts index 5509c5d3e0..cbbce9ea73 100644 --- a/src/vs/workbench/services/dialogs/browser/dialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/dialogService.ts @@ -129,7 +129,7 @@ export class DialogService implements IDialogService { navigator.userAgent ); - const { choice } = await this.show(Severity.Info, this.productService.nameLong, [nls.localize('copy', "Copy"), nls.localize('ok', "OK")], { detail }); + const { choice } = await this.show(Severity.Info, this.productService.nameLong, [nls.localize('copy', "Copy"), nls.localize('ok', "OK")], { detail, cancelId: 1 }); if (choice === 0) { this.clipboardService.writeText(detail); diff --git a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index 0f74527243..efbc4ea0a5 100644 --- a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -35,6 +35,7 @@ import { ICommandHandler } from 'vs/platform/commands/common/commands'; import { ITextFileService, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { toResource } from 'vs/workbench/common/editor'; +import { normalizeDriveLetter } from 'vs/base/common/labels'; export namespace OpenLocalFileCommand { export const ID = 'workbench.action.files.openLocalFile'; @@ -255,16 +256,6 @@ export class SimpleFileDialog { homedir = resources.dirname(this.options.defaultUri); this.trailing = resources.basename(this.options.defaultUri); } - // append extension - if (isSave && !ext && this.options.filters) { - for (let i = 0; i < this.options.filters.length; i++) { - if (this.options.filters[i].extensions[0] !== '*') { - ext = '.' + this.options.filters[i].extensions[0]; - this.trailing = this.trailing ? this.trailing + ext : ext; - break; - } - } - } } return new Promise(async (resolve) => { @@ -486,7 +477,7 @@ export class SimpleFileDialog { const newPath = this.pathFromUri(item.uri); 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); + this.insertText(newPath, this.basenameWithTrailingSlash(item.uri)); } else if ((item.label === '..') && startsWithIgnoreCase(this.filePickBox.value, newPath)) { this.filePickBox.valueSelection = [newPath.length, this.filePickBox.value.length]; this.insertText(newPath, ''); @@ -602,7 +593,7 @@ export class SimpleFileDialog { this.autoCompletePathSegment = ''; return false; } - const itemBasename = this.trimTrailingSlash(quickPickItem.label); + const itemBasename = quickPickItem.label; // Either force the autocomplete, or the old value should be one smaller than the new value and match the new value. if (itemBasename === '..') { // Don't match on the up directory item ever. @@ -621,7 +612,7 @@ export class SimpleFileDialog { this.autoCompletePathSegment = ''; this.filePickBox.activeItems = [quickPickItem]; return true; - } else if (force && (!equalsIgnoreCase(quickPickItem.label, (this.userEnteredPathSegment + this.autoCompletePathSegment)))) { + } else if (force && (!equalsIgnoreCase(this.basenameWithTrailingSlash(quickPickItem.uri), (this.userEnteredPathSegment + this.autoCompletePathSegment)))) { this.userEnteredPathSegment = ''; this.autoCompletePathSegment = this.trimTrailingSlash(itemBasename); this.activeItem = quickPickItem; @@ -807,7 +798,7 @@ export class SimpleFileDialog { } private pathFromUri(uri: URI, endWithSeparator: boolean = false): string { - let result: string = uri.fsPath.replace(/\n/g, ''); + let result: string = normalizeDriveLetter(uri.fsPath).replace(/\n/g, ''); if (this.separator === '/') { result = result.replace(/\\/g, this.separator); } else { @@ -848,7 +839,7 @@ export class SimpleFileDialog { } private createBackItem(currFolder: URI): FileQuickPickItem | null { - const parentFolder = resources.dirname(currFolder)!; + const parentFolder = resources.dirname(currFolder); if (!resources.isEqual(currFolder, parentFolder, true)) { return { label: '..', uri: resources.addTrailingPathSeparator(parentFolder, this.separator), isFolder: true }; } @@ -913,7 +904,7 @@ export class SimpleFileDialog { try { const stat = await this.fileService.resolve(fullPath); if (stat.isDirectory) { - filename = this.basenameWithTrailingSlash(fullPath); + filename = resources.basename(fullPath); fullPath = resources.addTrailingPathSeparator(fullPath, this.separator); return { label: filename, uri: fullPath, isFolder: true, iconClasses: getIconClasses(this.modelService, this.modeService, fullPath || undefined, FileKind.FOLDER) }; } else if (!stat.isDirectory && this.allowFileSelection && this.filterFile(fullPath)) { diff --git a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts index 4660bde819..7f72a51970 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts @@ -43,10 +43,11 @@ export class DialogService implements IDialogService { _serviceBrand: undefined; - private impl: IDialogService; + private nativeImpl: IDialogService; + private customImpl: IDialogService; constructor( - @IConfigurationService configurationService: IConfigurationService, + @IConfigurationService private configurationService: IConfigurationService, @ILogService logService: ILogService, @ILayoutService layoutService: ILayoutService, @IThemeService themeService: IThemeService, @@ -56,27 +57,32 @@ export class DialogService implements IDialogService { @IClipboardService clipboardService: IClipboardService, @IElectronService electronService: IElectronService ) { + this.customImpl = new HTMLDialogService(logService, layoutService, themeService, keybindingService, productService, clipboardService); + this.nativeImpl = new NativeDialogService(logService, sharedProcessService, electronService, clipboardService); + } - // Use HTML based dialogs - if (configurationService.getValue('workbench.dialogs.customEnabled') === true) { - this.impl = new HTMLDialogService(logService, layoutService, themeService, keybindingService, productService, clipboardService); - } - // Electron dialog service - else { - this.impl = new NativeDialogService(logService, sharedProcessService, electronService, clipboardService); - } + private get useCustomDialog(): boolean { + return this.configurationService.getValue('workbench.dialogs.customEnabled') === true; } confirm(confirmation: IConfirmation): Promise { - return this.impl.confirm(confirmation); + if (this.useCustomDialog) { + return this.customImpl.confirm(confirmation); + } + + return this.nativeImpl.confirm(confirmation); } show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions | undefined): Promise { - return this.impl.show(severity, message, buttons, options); + if (this.useCustomDialog) { + return this.customImpl.show(severity, message, buttons, options); + } + + return this.nativeImpl.show(severity, message, buttons, options); } about(): Promise { - return this.impl.about(); + return this.nativeImpl.about(); } } diff --git a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts index 1f7a567730..3b07328a29 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts @@ -46,8 +46,8 @@ export class FileDialogService extends AbstractFileDialogService implements IFil private shouldUseSimplified(schema: string): { useSimplified: boolean, isSetting: boolean } { const setting = (this.configurationService.getValue('files.simpleDialog.enable') === true); - - return { useSimplified: (schema !== Schemas.file) || setting, isSetting: (schema === Schemas.file) && setting }; + const newWindowSetting = (this.configurationService.getValue('window.openFilesInNewWindow') === 'on'); + return { useSimplified: (schema !== Schemas.file) || setting, isSetting: newWindowSetting }; } async pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise { @@ -110,7 +110,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil return this.pickFileToSaveSimplified(schema, options); } else { const result = await this.electronService.showSaveDialog(this.toNativeSaveDialogOptions(options)); - if (result && result.filePath) { + if (result && !result.canceled && result.filePath) { return URI.file(result.filePath); } } @@ -134,7 +134,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil } const result = await this.electronService.showSaveDialog(this.toNativeSaveDialogOptions(options)); - if (result && result.filePath) { + if (result && !result.canceled && result.filePath) { return URI.file(result.filePath); } @@ -149,7 +149,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil const defaultUri = options.defaultUri; - const newOptions: OpenDialogOptions = { + const newOptions: OpenDialogOptions & { properties: string[] } = { title: options.title, defaultPath: defaultUri && defaultUri.fsPath, buttonLabel: options.openLabel, @@ -157,18 +157,18 @@ export class FileDialogService extends AbstractFileDialogService implements IFil properties: [] }; - newOptions.properties!.push('createDirectory'); + newOptions.properties.push('createDirectory'); if (options.canSelectFiles) { - newOptions.properties!.push('openFile'); + newOptions.properties.push('openFile'); } if (options.canSelectFolders) { - newOptions.properties!.push('openDirectory'); + newOptions.properties.push('openDirectory'); } if (options.canSelectMany) { - newOptions.properties!.push('multiSelections'); + newOptions.properties.push('multiSelections'); } const result = await this.electronService.showOpenDialog(newOptions); diff --git a/src/vs/workbench/services/editor/browser/codeEditorService.ts b/src/vs/workbench/services/editor/browser/codeEditorService.ts index 4ac259e09a..76cd67b2c0 100644 --- a/src/vs/workbench/services/editor/browser/codeEditorService.ts +++ b/src/vs/workbench/services/editor/browser/codeEditorService.ts @@ -22,7 +22,7 @@ export class CodeEditorService extends CodeEditorServiceImpl { super(themeService); } - getActiveCodeEditor(): ICodeEditor | undefined { + getActiveCodeEditor(): ICodeEditor | null { const activeTextEditorWidget = this.editorService.activeTextEditorWidget; if (isCodeEditor(activeTextEditorWidget)) { return activeTextEditorWidget; @@ -32,10 +32,10 @@ export class CodeEditorService extends CodeEditorServiceImpl { return activeTextEditorWidget.getModifiedEditor(); } - return undefined; + return null; } - openCodeEditor(input: IResourceInput, source: ICodeEditor | undefined, sideBySide?: boolean): Promise { + openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { // Special case: If the active editor is a diff editor and the request to open originates and // targets the modified side of it, we just apply the request there to prevent opening the modified @@ -62,7 +62,7 @@ export class CodeEditorService extends CodeEditorServiceImpl { return this.doOpenCodeEditor(input, source, sideBySide); } - private async doOpenCodeEditor(input: IResourceInput, _source: ICodeEditor | undefined, sideBySide?: boolean): Promise { + private async doOpenCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { const control = await this.editorService.openEditor(input, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); if (control) { const widget = control.getControl(); @@ -71,7 +71,7 @@ export class CodeEditorService extends CodeEditorServiceImpl { } } - return undefined; + return null; } } diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 93cdd0bcd3..48c435b0bd 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -15,7 +15,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { Schemas } from 'vs/base/common/network'; import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { basename } from 'vs/base/common/resources'; +import { basename, isEqual } from 'vs/base/common/resources'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { localize } from 'vs/nls'; import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -153,8 +153,9 @@ export class EditorService extends Disposable implements EditorServiceImpl { for (const handler of this.openEditorHandlers) { const result = handler(event.editor, event.options, group); - if (result && result.override) { - event.prevent((() => result.override!.then(editor => withNullAsUndefined(editor)))); + const override = result ? result.override : undefined; + if (override) { + event.prevent((() => override.then(editor => withNullAsUndefined(editor)))); break; } } @@ -307,7 +308,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { const groupsByLastActive = this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE); // Respect option to reveal an editor if it is already visible in any group - if (options && options.revealIfVisible) { + if (options?.revealIfVisible) { for (const group of groupsByLastActive) { if (group.isActive(input)) { targetGroup = group; @@ -319,7 +320,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Respect option to reveal an editor if it is open (not necessarily visible) // Still prefer to reveal an editor in a group where the editor is active though. if (!targetGroup) { - if ((options && options.revealIfOpened) || this.configurationService.getValue('workbench.editor.revealIfOpen')) { + if (options?.revealIfOpened || this.configurationService.getValue('workbench.editor.revealIfOpen')) { let groupWithInputActive: IEditorGroup | undefined = undefined; let groupWithInputOpened: IEditorGroup | undefined = undefined; @@ -465,7 +466,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { } const resourceInput = editor as IResourceInput | IUntitledResourceInput; - if (resourceInput.resource && resource.toString() === resourceInput.resource.toString()) { + if (resourceInput.resource && isEqual(resource, resourceInput.resource)) { return editorInGroup; } } diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index a4154d82b6..b7deef6b4c 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -43,7 +43,7 @@ export class TestEditorInput extends EditorInput implements IFileEditorInput { resolve(): Promise { return Promise.resolve(null); } matches(other: TestEditorInput): boolean { return other && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } setEncoding(encoding: string) { } - getEncoding(): string { return null!; } + getEncoding() { return undefined; } setPreferredEncoding(encoding: string) { } setMode(mode: string) { } setPreferredMode(mode: string) { } diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index e0cfa7b6cb..44b3ed0d1d 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -57,7 +57,7 @@ export class TestEditorInput extends EditorInput implements IFileEditorInput { resolve(): Promise { return !this.fails ? Promise.resolve(null) : Promise.reject(new Error('fails')); } matches(other: TestEditorInput): boolean { return other && other.resource && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } setEncoding(encoding: string) { } - getEncoding(): string { return null!; } + getEncoding() { return undefined; } setPreferredEncoding(encoding: string) { } setMode(mode: string) { } setPreferredMode(mode: string) { } diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index b1d7e9405b..8a3bd8ebe4 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -16,22 +16,23 @@ import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platf import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; import product from 'vs/platform/product/common/product'; +import { serializableToMap } from 'vs/base/common/map'; export class BrowserWindowConfiguration implements IWindowConfiguration { - _: any[]; + _!: any[]; - machineId: string; - windowId: number; - logLevel: LogLevel; + machineId!: string; + windowId!: number; + logLevel!: LogLevel; - mainPid: number; + mainPid!: number; - appRoot: string; - execPath: string; + appRoot!: string; + execPath!: string; isInitialStartup?: boolean; - userEnv: IProcessEnvironment; + userEnv!: IProcessEnvironment; nodeCachedDataDir?: string; backupPath?: string; @@ -54,7 +55,7 @@ export class BrowserWindowConfiguration implements IWindowConfiguration { perfStartTime?: number; perfAppReady?: number; perfWindowLoadTime?: number; - perfEntries: ExportData; + perfEntries!: ExportData; filesToOpenOrCreate?: IPath[]; filesToDiff?: IPath[]; @@ -88,7 +89,7 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment this.userDataSyncLogResource = joinPath(options.logsPath, 'userDataSync.log'); this.keybindingsResource = joinPath(this.userRoamingDataHome, 'keybindings.json'); this.keyboardLayoutResource = joinPath(this.userRoamingDataHome, 'keyboardLayout.json'); - this.localeResource = joinPath(this.userRoamingDataHome, 'locale.json'); + this.argvResource = joinPath(this.userRoamingDataHome, 'argv.json'); this.backupHome = joinPath(this.userRoamingDataHome, BACKUPS); this.configuration.backupWorkspaceResource = joinPath(this.backupHome, options.workspaceId); this.configuration.connectionToken = options.connectionToken || getCookieValue('vscode-tkn'); @@ -100,32 +101,53 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment this.untitledWorkspacesHome = URI.from({ scheme: Schemas.untitled, path: 'Workspaces' }); - if (document && document.location && document.location.search) { - const map = new Map(); - const query = document.location.search.substring(1); - const vars = query.split('&'); - for (let p of vars) { - const pair = p.split('='); - if (pair.length >= 2) { - map.set(pair[0], decodeURIComponent(pair[1])); + // Fill in selected extra environmental properties + if (options.workspaceProvider && Array.isArray(options.workspaceProvider.payload)) { + const environment = serializableToMap(options.workspaceProvider.payload); + for (const [key, value] of environment) { + switch (key) { + case 'extensionDevelopmentPath': + this.extensionDevelopmentLocationURI = [URI.parse(value)]; + this.isExtensionDevelopment = true; + break; + case 'debugId': + this.debugExtensionHost.debugId = value; + break; + case 'inspect-brk-extensions': + this.debugExtensionHost.port = parseInt(value); + this.debugExtensionHost.break = false; + break; } } + } else { + // TODO@Ben remove me once environment is adopted + if (document && document.location && document.location.search) { + const map = new Map(); + const query = document.location.search.substring(1); + const vars = query.split('&'); + for (let p of vars) { + const pair = p.split('='); + if (pair.length >= 2) { + map.set(pair[0], decodeURIComponent(pair[1])); + } + } - const edp = map.get('edp'); - if (edp) { - this.extensionDevelopmentLocationURI = [URI.parse(edp)]; - this.isExtensionDevelopment = true; - } + const edp = map.get('extensionDevelopmentPath'); + if (edp) { + this.extensionDevelopmentLocationURI = [URI.parse(edp)]; + this.isExtensionDevelopment = true; + } - const di = map.get('di'); - if (di) { - this.debugExtensionHost.debugId = di; - } + const di = map.get('debugId'); + if (di) { + this.debugExtensionHost.debugId = di; + } - const ibe = map.get('ibe'); - if (ibe) { - this.debugExtensionHost.port = parseInt(ibe); - this.debugExtensionHost.break = false; + const ibe = map.get('inspect-brk-extensions'); + if (ibe) { + this.debugExtensionHost.port = parseInt(ibe); + this.debugExtensionHost.break = false; + } } } } @@ -133,53 +155,52 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment untitledWorkspacesHome: URI; extensionTestsLocationURI?: URI; args: any; - execPath: string; - cliPath: string; + execPath!: string; + cliPath!: string; appRoot: string; - userHome: string; - userDataPath: string; + userHome!: string; + userDataPath!: string; appNameLong: string; appQuality?: string; - appSettingsHome: URI; + appSettingsHome!: URI; userRoamingDataHome: URI; settingsResource: URI; keybindingsResource: URI; keyboardLayoutResource: URI; - localeResource: URI; + argvResource: URI; settingsSyncPreviewResource: URI; userDataSyncLogResource: URI; - machineSettingsHome: URI; - machineSettingsResource: URI; - globalStorageHome: string; - workspaceStorageHome: string; + machineSettingsHome!: URI; + machineSettingsResource!: URI; + globalStorageHome!: string; + workspaceStorageHome!: string; backupHome: URI; - backupWorkspacesPath: string; - workspacesHome: string; - isExtensionDevelopment: boolean; - disableExtensions: boolean | string[]; - builtinExtensionsPath: string; + backupWorkspacesPath!: string; + workspacesHome!: string; + isExtensionDevelopment!: boolean; + disableExtensions!: boolean | string[]; + builtinExtensionsPath!: string; extensionsPath?: string; extensionDevelopmentLocationURI?: URI[]; extensionTestsPath?: string; debugExtensionHost: IExtensionHostDebugParams; - debugSearch: IDebugParams; - logExtensionHostCommunication: boolean; - isBuilt: boolean; - wait: boolean; - status: boolean; + debugSearch!: IDebugParams; + logExtensionHostCommunication!: boolean; + isBuilt!: boolean; + wait!: boolean; + status!: boolean; log?: string; logsPath: string; - verbose: boolean; - skipGettingStarted: boolean; - skipReleaseNotes: boolean; - mainIPCHandle: string; - sharedIPCHandle: string; + verbose!: boolean; + skipReleaseNotes!: boolean; + mainIPCHandle!: string; + sharedIPCHandle!: string; nodeCachedDataDir?: string; - installSourcePath: string; - disableUpdates: boolean; - disableCrashReporter: boolean; + installSourcePath!: string; + disableUpdates!: boolean; + disableCrashReporter!: boolean; driverHandle?: string; - driverVerbose: boolean; + driverVerbose!: boolean; galleryMachineIdResource?: URI; readonly logFile: URI; diff --git a/src/vs/workbench/services/environment/common/environmentService.ts b/src/vs/workbench/services/environment/common/environmentService.ts index a1ab511ae9..47ea9e2f40 100644 --- a/src/vs/workbench/services/environment/common/environmentService.ts +++ b/src/vs/workbench/services/environment/common/environmentService.ts @@ -28,7 +28,6 @@ export interface IWorkbenchEnvironmentService extends IEnvironmentService { readonly webviewResourceRoot: string; readonly webviewCspSource: string; - readonly skipGettingStarted: boolean | undefined; readonly skipReleaseNotes: boolean | undefined; } diff --git a/src/vs/workbench/services/environment/node/environmentService.ts b/src/vs/workbench/services/environment/node/environmentService.ts index 224ccfdf70..a338f71122 100644 --- a/src/vs/workbench/services/environment/node/environmentService.ts +++ b/src/vs/workbench/services/environment/node/environmentService.ts @@ -36,8 +36,6 @@ export class WorkbenchEnvironmentService extends EnvironmentService implements I this.configuration.backupWorkspaceResource = this.configuration.backupPath ? toBackupWorkspaceResource(this.configuration.backupPath, this) : undefined; } - get skipGettingStarted(): boolean { return !!this.args['skip-getting-started']; } - get skipReleaseNotes(): boolean { return !!this.args['skip-release-notes']; } @memoize diff --git a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts index ef0b53d34c..12739940e7 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts @@ -6,7 +6,7 @@ import { localize } from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionIdentifier, DidInstallExtensionEvent, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionEnablementService, EnablementState, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; @@ -43,7 +43,6 @@ export class ExtensionEnablementService extends Disposable implements IExtension super(); this.storageManger = this._register(new StorageManager(storageService)); this._register(this.storageManger.onDidChange(extensions => this.onDidChangeStorage(extensions))); - this._register(extensionManagementService.onDidInstallExtension(this._onDidInstallExtension, this)); this._register(extensionManagementService.onDidUninstallExtension(this._onDidUninstallExtension, this)); } @@ -286,16 +285,6 @@ export class ExtensionEnablementService extends Disposable implements IExtension this._onEnablementChanged.fire(extensions); } - private _onDidInstallExtension(event: DidInstallExtensionEvent): void { - if (event.local && event.operation === InstallOperation.Install) { - const wasDisabled = !this.isEnabled(event.local); - this._reset(event.local.identifier); - if (wasDisabled) { - this._onEnablementChanged.fire([event.local]); - } - } - } - private _onDidUninstallExtension({ identifier, error }: DidUninstallExtensionEvent): void { if (!error) { this._reset(identifier); 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 38f5510b81..2f79c9193c 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 @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import * as sinon from 'sinon'; -import { IExtensionManagementService, DidUninstallExtensionEvent, ILocalExtension, DidInstallExtensionEvent, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, DidUninstallExtensionEvent, ILocalExtension, DidInstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionEnablementService, EnablementState, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionEnablementService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; @@ -71,12 +71,11 @@ suite('ExtensionEnablementService Test', () => { let testObject: IExtensionEnablementService; const didUninstallEvent = new Emitter(); - const didInstallEvent = new Emitter(); setup(() => { instantiationService = new TestInstantiationService(); instantiationService.stub(IConfigurationService, new TestConfigurationService()); - instantiationService.stub(IExtensionManagementService, { onDidUninstallExtension: didUninstallEvent.event, onDidInstallExtension: didInstallEvent.event, getInstalled: () => Promise.resolve([] as ILocalExtension[]) } as IExtensionManagementService); + instantiationService.stub(IExtensionManagementService, { onDidUninstallExtension: didUninstallEvent.event, getInstalled: () => Promise.resolve([] as ILocalExtension[]) } as IExtensionManagementService); instantiationService.stub(IExtensionManagementServerService, { localExtensionManagementServer: { extensionManagementService: instantiationService.get(IExtensionManagementService) @@ -338,90 +337,6 @@ suite('ExtensionEnablementService Test', () => { assert.equal(testObject.getEnablementState(extension), EnablementState.EnabledGlobally); }); - test('test installing an extension re-eanbles it when disabled globally', async () => { - const local = aLocalExtension('pub.a'); - await testObject.setEnablement([local], EnablementState.DisabledGlobally); - didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Install }); - assert.ok(testObject.isEnabled(local)); - assert.equal(testObject.getEnablementState(local), EnablementState.EnabledGlobally); - }); - - test('test updating an extension does not re-eanbles it when disabled globally', async () => { - const local = aLocalExtension('pub.a'); - await testObject.setEnablement([local], EnablementState.DisabledGlobally); - didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Update }); - assert.ok(!testObject.isEnabled(local)); - assert.equal(testObject.getEnablementState(local), EnablementState.DisabledGlobally); - }); - - test('test installing an extension fires enablement change event when disabled globally', async () => { - const local = aLocalExtension('pub.a'); - await testObject.setEnablement([local], EnablementState.DisabledGlobally); - return new Promise((c, e) => { - testObject.onEnablementChanged(([e]) => { - if (e.identifier.id === local.identifier.id) { - c(); - } - }); - didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Install }); - }); - }); - - test('test updating an extension does not fires enablement change event when disabled globally', async () => { - const target = sinon.spy(); - const local = aLocalExtension('pub.a'); - await testObject.setEnablement([local], EnablementState.DisabledGlobally); - testObject.onEnablementChanged(target); - didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Update }); - assert.ok(!target.called); - }); - - test('test installing an extension re-eanbles it when workspace disabled', async () => { - const local = aLocalExtension('pub.a'); - await testObject.setEnablement([local], EnablementState.DisabledWorkspace); - didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Install }); - assert.ok(testObject.isEnabled(local)); - assert.equal(testObject.getEnablementState(local), EnablementState.EnabledGlobally); - }); - - test('test updating an extension does not re-eanbles it when workspace disabled', async () => { - const local = aLocalExtension('pub.a'); - await testObject.setEnablement([local], EnablementState.DisabledWorkspace); - didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Update }); - assert.ok(!testObject.isEnabled(local)); - assert.equal(testObject.getEnablementState(local), EnablementState.DisabledWorkspace); - }); - - test('test installing an extension fires enablement change event when workspace disabled', async () => { - const local = aLocalExtension('pub.a'); - await testObject.setEnablement([local], EnablementState.DisabledWorkspace); - return new Promise((c, e) => { - testObject.onEnablementChanged(([e]) => { - if (e.identifier.id === local.identifier.id) { - c(); - } - }); - didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Install }); - }); - }); - - test('test updating an extension does not fires enablement change event when workspace disabled', async () => { - const target = sinon.spy(); - const local = aLocalExtension('pub.a'); - await testObject.setEnablement([local], EnablementState.DisabledWorkspace); - testObject.onEnablementChanged(target); - didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Update }); - assert.ok(!target.called); - }); - - test('test installing an extension should not fire enablement change event when extension is not disabled', async () => { - const target = sinon.spy(); - const local = aLocalExtension('pub.a'); - testObject.onEnablementChanged(target); - didInstallEvent.fire({ local, identifier: local.identifier, operation: InstallOperation.Install }); - assert.ok(!target.called); - }); - test('test remove an extension from disablement list when uninstalled', async () => { const extension = aLocalExtension('pub.a'); await testObject.setEnablement([extension], EnablementState.DisabledWorkspace); @@ -480,7 +395,7 @@ suite('ExtensionEnablementService Test', () => { test('test extension is disabled when disabled in enviroment', async () => { const extension = aLocalExtension('pub.a'); instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService); - instantiationService.stub(IExtensionManagementService, { onDidUninstallExtension: didUninstallEvent.event, onDidInstallExtension: didInstallEvent.event, getInstalled: () => Promise.resolve([extension, aLocalExtension('pub.b')]) } as IExtensionManagementService); + instantiationService.stub(IExtensionManagementService, { onDidUninstallExtension: didUninstallEvent.event, getInstalled: () => Promise.resolve([extension, aLocalExtension('pub.b')]) } as IExtensionManagementService); testObject = new TestExtensionEnablementService(instantiationService); assert.ok(!testObject.isEnabled(extension)); assert.deepEqual(testObject.getEnablementState(extension), EnablementState.DisabledByEnvironemt); diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index 605abf1987..398a350117 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -9,13 +9,13 @@ import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/li import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, ExtensionsLabel } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionEnablementService, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { INotificationHandle, INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { IURLHandler, IURLService } from 'vs/platform/url/common/url'; +import { IURLHandler, IURLService, IOpenURLOptions } from 'vs/platform/url/common/url'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -23,16 +23,47 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContribution, Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; const FIVE_MINUTES = 5 * 60 * 1000; const THIRTY_SECONDS = 30 * 1000; const URL_TO_HANDLE = 'extensionUrlHandler.urlToHandle'; const CONFIRMED_EXTENSIONS_CONFIGURATION_KEY = 'extensions.confirmedUriHandlerExtensionIds'; +const CONFIRMED_EXTENSIONS_STORAGE_KEY = 'extensionUrlHandler.confirmedExtensions'; function isExtensionId(value: string): boolean { return /^[a-z0-9][a-z0-9\-]*\.[a-z0-9][a-z0-9\-]*$/i.test(value); } +class ConfirmedExtensionIdStorage { + + get extensions(): string[] { + const confirmedExtensionIdsJson = this.storageService.get(CONFIRMED_EXTENSIONS_STORAGE_KEY, StorageScope.GLOBAL, '[]'); + + try { + return JSON.parse(confirmedExtensionIdsJson); + } catch { + return []; + } + } + + constructor(private storageService: IStorageService) { } + + has(id: string): boolean { + return this.extensions.indexOf(id) > -1; + } + + add(id: string): void { + this.set([...this.extensions, id]); + } + + set(ids: string[]): void { + this.storageService.store(CONFIRMED_EXTENSIONS_STORAGE_KEY, JSON.stringify(ids), StorageScope.GLOBAL); + } +} + export const IExtensionUrlHandler = createDecorator('extensionUrlHandler'); export interface IExtensionUrlHandler { @@ -56,6 +87,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { private extensionHandlers = new Map(); private uriBuffer = new Map(); + private storage: ConfirmedExtensionIdStorage; private disposable: IDisposable; constructor( @@ -70,11 +102,13 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { @IStorageService private readonly storageService: IStorageService, @IConfigurationService private readonly configurationService: IConfigurationService ) { + this.storage = new ConfirmedExtensionIdStorage(storageService); + const interval = setInterval(() => this.garbageCollect(), THIRTY_SECONDS); const urlToHandleValue = this.storageService.get(URL_TO_HANDLE, StorageScope.WORKSPACE); if (urlToHandleValue) { this.storageService.remove(URL_TO_HANDLE, StorageScope.WORKSPACE); - this.handleURL(URI.revive(JSON.parse(urlToHandleValue)), true); + this.handleURL(URI.revive(JSON.parse(urlToHandleValue)), { trusted: true }); } this.disposable = combinedDisposable( @@ -86,7 +120,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { setTimeout(() => cache.forEach(uri => this.handleURL(uri))); } - async handleURL(uri: URI, confirmed?: boolean): Promise { + async handleURL(uri: URI, options?: IOpenURLOptions): Promise { if (!isExtensionId(uri.authority)) { return false; } @@ -100,12 +134,14 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { return true; } - if (!confirmed) { - const confirmedExtensionIds = this.getConfirmedExtensionIds(); - confirmed = confirmedExtensionIds.has(ExtensionIdentifier.toKey(extensionId)); + let showConfirm: boolean; + if (options && options.trusted) { + showConfirm = false; + } else { + showConfirm = !this.isConfirmed(ExtensionIdentifier.toKey(extensionId)); } - if (!confirmed) { + if (showConfirm) { let uriString = uri.toString(); // {{SQL CARBON EDIT}} - Begin @@ -135,7 +171,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { } if (result.checkboxChecked) { - await this.addConfirmedExtensionIdToStorage(extensionId); + this.storage.add(ExtensionIdentifier.toKey(extensionId)); } } @@ -144,7 +180,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { if (handler) { if (!wasHandlerAvailable) { // forward it directly - return await handler.handleURL(uri); + return await handler.handleURL(uri, options); } // let the ExtensionUrlHandler instance handle this @@ -296,11 +332,12 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { this.uriBuffer = uriBuffer; } - private getConfirmedExtensionIds(): Set { - const ids = this.getConfirmedExtensionIdsFromConfiguration() - .map(extensionId => ExtensionIdentifier.toKey(extensionId)); + private isConfirmed(id: string): boolean { + if (this.storage.has(id)) { + return true; + } - return new Set(ids); + return this.getConfirmedExtensionIdsFromConfiguration().indexOf(id) > -1; } private getConfirmedExtensionIdsFromConfiguration(): Array { @@ -313,14 +350,6 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { return confirmedExtensionIds; } - private async addConfirmedExtensionIdToStorage(extensionId: string): Promise { - const confirmedExtensionIds = this.configurationService.getValue>(CONFIRMED_EXTENSIONS_CONFIGURATION_KEY); - const set = new Set(confirmedExtensionIds); - set.add(extensionId); - - await this.configurationService.updateValue(CONFIRMED_EXTENSIONS_CONFIGURATION_KEY, [...set.values()]); - } - dispose(): void { this.disposable.dispose(); this.extensionHandlers.clear(); @@ -351,11 +380,52 @@ class ExtensionUrlBootstrapHandler implements IWorkbenchContribution, IURLHandle ExtensionUrlBootstrapHandler.disposable = urlService.registerHandler(this); } - handleURL(uri: URI): Promise { + async handleURL(uri: URI): Promise { + if (!isExtensionId(uri.authority)) { + return false; + } + ExtensionUrlBootstrapHandler._cache.push(uri); - return Promise.resolve(true); + return true; } } const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(ExtensionUrlBootstrapHandler, LifecyclePhase.Ready); + +export class ManageAuthorizedExtensionURIsAction extends Action { + + static readonly ID = 'workbench.extensions.action.manageAuthorizedExtensionURIs'; + static readonly LABEL = localize('manage', "Manage Authorized Extension URIs..."); + + private storage: ConfirmedExtensionIdStorage; + + constructor( + id = ManageAuthorizedExtensionURIsAction.ID, + label = ManageAuthorizedExtensionURIsAction.LABEL, + @IStorageService readonly storageService: IStorageService, + @IQuickInputService private readonly quickInputService: IQuickInputService + ) { + super(id, label, undefined, true); + this.storage = new ConfirmedExtensionIdStorage(storageService); + } + + async run(): Promise { + const items = this.storage.extensions.map(label => ({ label, picked: true } as IQuickPickItem)); + + if (items.length === 0) { + return; + } + + const result = await this.quickInputService.pick(items, { canPickMany: true }); + + if (!result) { + return; + } + + this.storage.set(result.map(item => item.label)); + } +} + +const actionRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ManageAuthorizedExtensionURIsAction, ManageAuthorizedExtensionURIsAction.ID, ManageAuthorizedExtensionURIsAction.LABEL), `Extensions: Manage Authorized Extension URIs...`, ExtensionsLabel); diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts index 6d6e839588..e5129347f0 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts @@ -113,6 +113,10 @@ export class WebWorkerExtensionHostStarter implements IExtensionHostStarter { return undefined; } + enableInspectPort(): Promise { + return Promise.resolve(false); + } + private async _createExtHostInitData(): Promise { const [telemetryInfo, extensionDescriptions] = await Promise.all([this._telemetryService.getTelemetryInfo(), this._extensions]); const workspace = this._contextService.getWorkspace(); diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index d289d072af..13bbe713de 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -256,8 +256,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx return result; } - public getInspectPort(): number { - return 0; + public getInspectPort(_tryEnableInspector: boolean): Promise { + return Promise.resolve(0); } public async setRemoteEnvironment(env: { [key: string]: string | null }): Promise { @@ -459,9 +459,10 @@ class ProposedApiController { // Make enabled proposed API be lowercase for case insensitive comparison this.enableProposedApiFor = (environmentService.args['enable-proposed-api'] || []).map(id => id.toLowerCase()); - this.enableProposedApiForAll = !environmentService.isBuilt || - (!!environmentService.extensionDevelopmentLocationURI && productService.nameLong !== 'Visual Studio Code') || - (this.enableProposedApiFor.length === 0 && 'enable-proposed-api' in environmentService.args); + this.enableProposedApiForAll = + !environmentService.isBuilt || // always allow proposed API when running out of sources + (!!environmentService.extensionDevelopmentLocationURI && productService.quality !== 'stable') || // do not allow proposed API against stable builds when developing an extension + (this.enableProposedApiFor.length === 0 && 'enable-proposed-api' in environmentService.args); // always allow proposed API if --enable-proposed-api is provided without extension ID this.productAllowProposedApi = new Set(); if (isNonEmptyArray(productService.extensionAllowedProposedApi)) { diff --git a/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts b/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts index 7d0e77ccad..b0e8ade7c3 100644 --- a/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts @@ -118,12 +118,7 @@ export class ExtensionDescriptionRegistry { hasOnlyGoodArcs(id: string, good: Set): boolean { const dependencies = G.getArcs(id); - for (let i = 0; i < dependencies.length; i++) { - if (!good.has(dependencies[i])) { - return false; - } - } - return true; + return dependencies.every(dependency => good.has(dependency)); } getNodes(): string[] { diff --git a/src/vs/workbench/services/extensions/common/extensionHostMain.ts b/src/vs/workbench/services/extensions/common/extensionHostMain.ts index 72c7a0ac42..77bed26af1 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostMain.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostMain.ts @@ -6,7 +6,7 @@ import { timeout } from 'vs/base/common/async'; import * as errors from 'vs/base/common/errors'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { URI, setUriThrowOnMissingScheme } from 'vs/base/common/uri'; +import { URI } from 'vs/base/common/uri'; import { IURITransformer } from 'vs/base/common/uriIpc'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { IInitData, MainContext, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol'; @@ -22,10 +22,6 @@ import { IExtHostRpcService, ExtHostRpcService } from 'vs/workbench/api/common/e import { IURITransformerService, URITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; import { IExtHostExtensionService, IHostUtils } from 'vs/workbench/api/common/extHostExtensionService'; -// we don't (yet) throw when extensions parse -// uris that have no scheme -setUriThrowOnMissingScheme(false); - export interface IExitFn { (code?: number): any; } diff --git a/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts b/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts index ae28def4da..5e5f7c4358 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts @@ -240,8 +240,11 @@ export class ExtensionHostProcessManager extends Disposable { }); } - public getInspectPort(): number { + public async getInspectPort(tryEnableInspector: boolean): Promise { if (this._extensionHostProcessWorker) { + if (tryEnableInspector) { + await this._extensionHostProcessWorker.enableInspectPort(); + } let port = this._extensionHostProcessWorker.getInspectPort(); if (port) { return port; @@ -250,10 +253,6 @@ export class ExtensionHostProcessManager extends Disposable { return 0; } - public canProfileExtensionHost(): boolean { - return this._extensionHostProcessWorker && Boolean(this._extensionHostProcessWorker.getInspectPort()); - } - public async resolveAuthority(remoteAuthority: string): Promise { const authorityPlusIndex = remoteAuthority.indexOf('+'); if (authorityPlusIndex === -1) { diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index 5aee0ade8e..fc610a53c4 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -89,6 +89,7 @@ export interface IExtensionHostStarter { start(): Promise | null; getInspectPort(): number | undefined; + enableInspectPort(): Promise; dispose(): void; } @@ -212,7 +213,7 @@ export interface IExtensionService { * Return the inspect port or `0`, the latter means inspection * is not possible. */ - getInspectPort(): number; + getInspectPort(tryEnableInspector: boolean): Promise; /** * Restarts the extension host. @@ -270,7 +271,7 @@ export class NullExtensionService implements IExtensionService { getExtension() { return Promise.resolve(undefined); } readExtensionPointContributions(_extPoint: IExtensionPoint): Promise[]> { return Promise.resolve(Object.create(null)); } getExtensionsStatus(): { [id: string]: IExtensionsStatus; } { return Object.create(null); } - getInspectPort(): number { return 0; } + getInspectPort(_tryEnableInspector: boolean): Promise { return Promise.resolve(0); } restartExtensionHost(): void { } async setRemoteEnvironment(_env: { [key: string]: string | null }): Promise { } canAddExtension(): boolean { return false; } diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts index 1026b8259e..f7fba51085 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts @@ -226,6 +226,10 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH return undefined; } + enableInspectPort(): Promise { + return Promise.resolve(false); + } + dispose(): void { super.dispose(); diff --git a/src/vs/workbench/services/extensions/common/rpcProtocol.ts b/src/vs/workbench/services/extensions/common/rpcProtocol.ts index 593c6311d0..968c3e6bcd 100644 --- a/src/vs/workbench/services/extensions/common/rpcProtocol.ts +++ b/src/vs/workbench/services/extensions/common/rpcProtocol.ts @@ -58,7 +58,7 @@ const noop = () => { }; export class RPCProtocol extends Disposable implements IRPCProtocol { - private static UNRESPONSIVE_TIME = 3 * 1000; // 3s + private static readonly UNRESPONSIVE_TIME = 3 * 1000; // 3s private readonly _onDidChangeResponsiveState: Emitter = this._register(new Emitter()); public readonly onDidChangeResponsiveState: Event = this._onDidChangeResponsiveState.event; @@ -588,12 +588,7 @@ class MessageBuffer { class MessageIO { private static _arrayContainsBuffer(arr: any[]): boolean { - for (let i = 0, len = arr.length; i < len; i++) { - if (arr[i] instanceof VSBuffer) { - return true; - } - } - return false; + return arr.some(value => value instanceof VSBuffer); } public static serializeRequest(req: number, rpcId: number, method: string, args: any[], usesCancellationToken: boolean, replacer: JSONStringifyReplacer | null): VSBuffer { diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index da3f7f0c75..9e23965882 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -16,7 +16,7 @@ import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { IRemoteConsoleLog, log } from 'vs/base/common/console'; import { logRemoteEntry } from 'vs/workbench/services/extensions/common/remoteConsoleUtil'; -import { findFreePort, randomPort } from 'vs/base/node/ports'; +import { findFreePort } from 'vs/base/node/ports'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net'; import { generateRandomPipeName, NodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; @@ -45,6 +45,8 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { private readonly _onExit: Emitter<[number, string]> = new Emitter<[number, string]>(); public readonly onExit: Event<[number, string]> = this._onExit.event; + private readonly _onDidSetInspectPort = new Emitter(); + private readonly _toDispose = new DisposableStore(); private readonly _isExtensionDevHost: boolean; @@ -127,10 +129,10 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { if (!this._messageProtocol) { this._messageProtocol = Promise.all([ this._tryListenOnPipe(), - !this._environmentService.args['disable-inspect'] ? this._tryFindDebugPort() : Promise.resolve(null) + this._tryFindDebugPort() ]).then(data => { const pipeName = data[0]; - const portData = data[1]; + const portNumber = data[1]; const opts = { env: objects.mixin(objects.deepClone(process.env), { @@ -151,16 +153,11 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { silent: true }; - if (portData && portData.actual) { + if (portNumber !== 0) { opts.execArgv = [ '--nolazy', - (this._isExtensionDevDebugBrk ? '--inspect-brk=' : '--inspect=') + portData.actual + (this._isExtensionDevDebugBrk ? '--inspect-brk=' : '--inspect=') + portNumber ]; - if (!portData.expected) { - // No one asked for 'inspect' or 'inspect-brk', only us. We add another - // option such that the extension host can manipulate the execArgv array - opts.env.VSCODE_PREVENT_FOREIGN_INSPECT = true; - } } const crashReporterOptions = undefined; // TODO@electron pass this in as options to the extension host after verifying this actually works @@ -198,6 +195,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { } if (!this._inspectPort) { this._inspectPort = Number(inspectorUrlMatch[2]); + this._onDidSetInspectPort.fire(); } } else { console.group('Extension Host'); @@ -218,11 +216,12 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { this._extensionHostProcess.on('exit', (code: number, signal: string) => this._onExtHostProcessExit(code, signal)); // Notify debugger that we are ready to attach to the process if we run a development extension - if (portData) { - if (this._isExtensionDevHost && portData.actual && this._isExtensionDevDebug && this._environmentService.debugExtensionHost.debugId) { - this._extensionHostDebugService.attachSession(this._environmentService.debugExtensionHost.debugId, portData.actual); + if (portNumber) { + if (this._isExtensionDevHost && portNumber && this._isExtensionDevDebug && this._environmentService.debugExtensionHost.debugId) { + this._extensionHostDebugService.attachSession(this._environmentService.debugExtensionHost.debugId, portNumber); } - this._inspectPort = portData.actual; + this._inspectPort = portNumber; + this._onDidSetInspectPort.fire(); } // Help in case we fail to start it @@ -275,29 +274,31 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { /** * Find a free port if extension host debugging is enabled. */ - private _tryFindDebugPort(): Promise<{ expected: number; actual: number }> { - let expected: number; - let startPort = randomPort(); - if (typeof this._environmentService.debugExtensionHost.port === 'number') { - startPort = expected = this._environmentService.debugExtensionHost.port; + private async _tryFindDebugPort(): Promise { + + if (typeof this._environmentService.debugExtensionHost.port !== 'number') { + return 0; } - return new Promise(resolve => { - return findFreePort(startPort, 10 /* try 10 ports */, 5000 /* try up to 5 seconds */).then(port => { - if (!port) { - console.warn('%c[Extension Host] %cCould not find a free port for debugging', 'color: blue', 'color:'); - } else { - if (expected && port !== expected) { - console.warn(`%c[Extension Host] %cProvided debugging port ${expected} is not free, using ${port} instead.`, 'color: blue', 'color:'); - } - if (this._isExtensionDevDebugBrk) { - console.warn(`%c[Extension Host] %cSTOPPED on first line for debugging on port ${port}`, 'color: blue', 'color:'); - } else { - console.info(`%c[Extension Host] %cdebugger listening on port ${port}`, 'color: blue', 'color:'); - } - } - return resolve({ expected, actual: port }); - }); - }); + + const expected = this._environmentService.debugExtensionHost.port; + const port = await findFreePort(expected, 10 /* try 10 ports */, 5000 /* try up to 5 seconds */); + + if (!port) { + console.warn('%c[Extension Host] %cCould not find a free port for debugging', 'color: blue', 'color:'); + return 0; + } + + if (port !== expected) { + console.warn(`%c[Extension Host] %cProvided debugging port ${expected} is not free, using ${port} instead.`, 'color: blue', 'color:'); + } + if (this._isExtensionDevDebugBrk) { + console.warn(`%c[Extension Host] %cSTOPPED on first line for debugging on port ${port}`, 'color: blue', 'color:'); + } else { + console.info(`%c[Extension Host] %cdebugger listening on port ${port}`, 'color: blue', 'color:'); + } + return port; + + } private _tryExtHostHandshake(): Promise { @@ -467,6 +468,37 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { this._onExit.fire([code, signal]); } + public async enableInspectPort(): Promise { + if (typeof this._inspectPort === 'number') { + return true; + } + + if (!this._extensionHostProcess) { + return false; + } + + interface ProcessExt { + _debugProcess?(n: number): any; + } + + if (typeof (process)._debugProcess === 'function') { + // use (undocumented) _debugProcess feature of node + (process)._debugProcess!(this._extensionHostProcess.pid); + await Promise.race([Event.toPromise(this._onDidSetInspectPort.event), timeout(1000)]); + return typeof this._inspectPort === 'number'; + + } else if (!platform.isWindows) { + // use KILL USR1 on non-windows platforms (fallback) + this._extensionHostProcess.kill('SIGUSR1'); + await Promise.race([Event.toPromise(this._onDidSetInspectPort.event), timeout(1000)]); + return typeof this._inspectPort === 'number'; + + } else { + // not supported... + return false; + } + } + public getInspectPort(): number | undefined { return withNullAsUndefined(this._inspectPort); } diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 325b417b6d..e51432cf92 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -542,9 +542,9 @@ export class ExtensionService extends AbstractExtensionService implements IExten this._doHandleExtensionPoints(this._registry.getAllExtensionDescriptions()); } - public getInspectPort(): number { + public async getInspectPort(tryEnableInspector: boolean): Promise { if (this._extensionHostProcessManagers.length > 0) { - return this._extensionHostProcessManagers[0].getInspectPort(); + return this._extensionHostProcessManagers[0].getInspectPort(tryEnableInspector); } return 0; } diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index c140f52483..4f8aed30ad 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -222,7 +222,7 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise { unhandledPromises.splice(idx, 1); console.warn(`rejected promise not handled within 1 second: ${e}`); - if (e.stack) { + if (e && e.stack) { console.warn(`stack trace: ${e.stack}`); } onUnexpectedError(reason); @@ -275,20 +275,6 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise { const protocol = await createExtHostProtocol(); diff --git a/src/vs/workbench/services/extensions/node/extensionPoints.ts b/src/vs/workbench/services/extensions/node/extensionPoints.ts index 499cef4250..c93d977c30 100644 --- a/src/vs/workbench/services/extensions/node/extensionPoints.ts +++ b/src/vs/workbench/services/extensions/node/extensionPoints.ts @@ -51,7 +51,7 @@ class ExtensionManifestParser extends ExtensionManifestHandler { return pfs.readFile(this._absoluteManifestPath).then((manifestContents) => { const errors: json.ParseError[] = []; const manifest = json.parse(manifestContents.toString(), errors); - if (errors.length === 0) { + if (!!manifest && errors.length === 0) { if (manifest.__metadata) { manifest.uuid = manifest.__metadata.id; } @@ -397,15 +397,7 @@ class ExtensionManifestValidator extends ExtensionManifestHandler { } private static _isStringArray(arr: string[]): boolean { - if (!Array.isArray(arr)) { - return false; - } - for (let i = 0, len = arr.length; i < len; i++) { - if (typeof arr[i] !== 'string') { - return false; - } - } - return true; + return Array.isArray(arr) && arr.every(value => typeof value === 'string'); } } diff --git a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts index fef3f893f2..b07a8ed1c5 100644 --- a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts +++ b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts @@ -35,9 +35,6 @@ self.postMessage = () => console.trace(`'postMessage' has been blocked`); const nativeAddEventLister = addEventListener.bind(self); self.addEventLister = () => console.trace(`'addEventListener' has been blocked`); -self.indexedDB.open = () => console.trace(`'indexedDB.open' has been blocked`); -self.caches.open = () => console.trace(`'indexedDB.caches' has been blocked`); - //#endregion --- const hostUtil = new class implements IHostUtils { diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index 471ab505b9..d831ca073f 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -59,7 +59,7 @@ export class TextEditorState { } justifiesNewPushState(other: TextEditorState, event?: ICursorPositionChangedEvent): boolean { - if (event && event.source === 'api') { + if (event?.source === 'api') { return true; // always let API source win (e.g. "Go to definition" should add a history entry) } @@ -120,7 +120,7 @@ export class HistoryService extends Disposable implements IHistoryService { private lastEditLocation: IStackEntry | undefined; - private history!: Array; + private history: Array = []; private recentlyClosedFiles: IRecentlyClosedFile[]; private loaded: boolean; private resourceFilter: ResourceGlobMatcher; @@ -227,7 +227,7 @@ export class HistoryService extends Disposable implements IHistoryService { } // Remember as last active editor (can be undefined if none opened) - this.lastActiveEditor = activeControl && activeControl.input && activeControl.group ? { editor: activeControl.input, groupId: activeControl.group.id } : undefined; + this.lastActiveEditor = activeControl?.input && activeControl.group ? { editor: activeControl.input, groupId: activeControl.group.id } : undefined; // Dispose old listeners this.activeEditorListeners.clear(); @@ -250,7 +250,11 @@ export class HistoryService extends Disposable implements IHistoryService { // Track the last edit location by tracking model content change events // Use a debouncer to make sure to capture the correct cursor position // after the model content has changed. - this.activeEditorListeners.add(Event.debounce(activeTextEditorWidget.onDidChangeModelContent, (last, event) => event, 0)((event => this.rememberLastEditLocation(activeEditor!, activeTextEditorWidget)))); + this.activeEditorListeners.add(Event.debounce(activeTextEditorWidget.onDidChangeModelContent, (last, event) => event, 0)((event => { + if (activeEditor) { + this.rememberLastEditLocation(activeEditor, activeTextEditorWidget); + } + }))); } } @@ -592,7 +596,7 @@ export class HistoryService extends Disposable implements IHistoryService { // stack but we need to keep our currentTextEditorState up to date with // the navigtion that occurs. if (this.navigatingInStack) { - if (codeEditor && control && control.input) { + if (codeEditor && control?.input) { this.currentTextEditorState = new TextEditorState(control.input, codeEditor.getSelection()); } else { this.currentTextEditorState = null; // we navigated to a non text editor @@ -603,7 +607,7 @@ export class HistoryService extends Disposable implements IHistoryService { else { // navigation inside text editor - if (codeEditor && control && control.input) { + if (codeEditor && control?.input) { this.handleTextEditorEvent(control, codeEditor, event); } @@ -611,7 +615,7 @@ export class HistoryService extends Disposable implements IHistoryService { else { this.currentTextEditorState = null; // at this time we have no active text editor view state - if (control && control.input) { + if (control?.input) { this.handleNonTextEditorEvent(control); } } @@ -845,7 +849,7 @@ export class HistoryService extends Disposable implements IHistoryService { const resourceInput = arg2 as IResourceInput; - return resourceInput && resourceInput.resource.toString() === resource.toString(); + return resourceInput?.resource.toString() === resource.toString(); } getHistory(): Array { @@ -924,7 +928,7 @@ export class HistoryService extends Disposable implements IHistoryService { // Editor input: via factory const { editorInputJSON } = serializedEditorHistoryEntry; - if (editorInputJSON && editorInputJSON.deserialized) { + if (editorInputJSON?.deserialized) { const factory = registry.getEditorInputFactory(editorInputJSON.typeId); if (factory) { const input = factory.deserialize(this.instantiationService, editorInputJSON.deserialized); @@ -996,7 +1000,7 @@ export class HistoryService extends Disposable implements IHistoryService { resource = (input as IResourceInput).resource; } - if (resource && resource.scheme === filterByScheme) { + if (resource?.scheme === filterByScheme) { return resource; } } diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index 28037e1440..8ad22183f2 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -33,13 +33,21 @@ export interface IWorkspaceProvider { */ readonly workspace: IWorkspace; + /** + * Arbitrary payload from the `IWorkspaceProvider.open` call. + */ + readonly payload?: object; + /** * Asks to open a workspace in the current or a new window. * * @param workspace the workspace to open. - * @param options wether to open inside the current window or a new window. + * @param options optional options for the workspace to open. + * - `reuse`: wether to open inside the current window or a new window + * - `payload`: arbitrary payload that should be made available + * to the opening window via the `IWorkspaceProvider.payload` property. */ - open(workspace: IWorkspace, options?: { reuse?: boolean }): Promise; + open(workspace: IWorkspace, options?: { reuse?: boolean, payload?: object }): Promise; } export class BrowserHostService extends Disposable implements IHostService { @@ -66,23 +74,21 @@ export class BrowserHostService extends Disposable implements IHostService { async open() { } }; } - - this.registerListeners(); } - private registerListeners(): void { + private _onDidChangeFocus: Event | undefined; + get onDidChangeFocus(): Event { + if (!this._onDidChangeFocus) { + const focusTracker = this._register(trackFocus(window)); + this._onDidChangeFocus = Event.any( + Event.map(focusTracker.onDidFocus, () => this.hasFocus), + Event.map(focusTracker.onDidBlur, () => this.hasFocus) + ); + } - // Track Focus on Window - const focusTracker = this._register(trackFocus(window)); - this._onDidChangeFocus = Event.any( - Event.map(focusTracker.onDidFocus, () => this.hasFocus), - Event.map(focusTracker.onDidBlur, () => this.hasFocus) - ); + return this._onDidChangeFocus; } - get onDidChangeFocus(): Event { return this._onDidChangeFocus; } - private _onDidChangeFocus: Event; - get hasFocus(): boolean { return document.hasFocus(); } @@ -138,7 +144,7 @@ export class BrowserHostService extends Disposable implements IHostService { private shouldReuse(options: IOpenWindowOptions = {}): boolean { const windowConfig = this.configurationService.getValue('window'); - const openFolderInNewWindowConfig = (windowConfig && windowConfig.openFoldersInNewWindow) || 'default' /* default */; + const openFolderInNewWindowConfig = windowConfig?.openFoldersInNewWindow || 'default' /* default */; let openFolderInNewWindow = !!options.forceNewWindow && !options.forceReuseWindow; if (!options.forceNewWindow && !options.forceReuseWindow && (openFolderInNewWindowConfig === 'on' || openFolderInNewWindowConfig === 'off')) { @@ -149,7 +155,7 @@ export class BrowserHostService extends Disposable implements IHostService { } private async doOpenEmptyWindow(options?: IOpenEmptyWindowOptions): Promise { - this.workspaceProvider.open(undefined, { reuse: options && options.forceReuseWindow }); + this.workspaceProvider.open(undefined, { reuse: options?.forceReuseWindow }); } async toggleFullScreen(): Promise { @@ -193,10 +199,6 @@ export class BrowserHostService extends Disposable implements IHostService { async reload(): Promise { window.location.reload(); } - - async closeWorkspace(): Promise { - return this.doOpenEmptyWindow({ forceReuseWindow: true }); - } } registerSingleton(IHostService, BrowserHostService, true); diff --git a/src/vs/workbench/services/host/browser/host.ts b/src/vs/workbench/services/host/browser/host.ts index f85a63713a..74678cd205 100644 --- a/src/vs/workbench/services/host/browser/host.ts +++ b/src/vs/workbench/services/host/browser/host.ts @@ -66,11 +66,5 @@ export interface IHostService { */ reload(): Promise; - /** - * Closes the currently opened folder/workspace and returns to an empty - * window. - */ - closeWorkspace(): Promise; - //#endregion } diff --git a/src/vs/workbench/services/host/electron-browser/desktopHostService.ts b/src/vs/workbench/services/host/electron-browser/desktopHostService.ts index b1846bafd3..365900da19 100644 --- a/src/vs/workbench/services/host/electron-browser/desktopHostService.ts +++ b/src/vs/workbench/services/host/electron-browser/desktopHostService.ts @@ -94,10 +94,6 @@ export class DesktopHostService extends Disposable implements IHostService { reload(): Promise { return this.electronService.reload(); } - - closeWorkspace(): Promise { - return this.electronService.closeWorkspace(); - } } registerSingleton(IHostService, DesktopHostService, true); diff --git a/src/vs/workbench/services/integrity/node/integrityService.ts b/src/vs/workbench/services/integrity/node/integrityService.ts index 90414ab786..1f25387158 100644 --- a/src/vs/workbench/services/integrity/node/integrityService.ts +++ b/src/vs/workbench/services/integrity/node/integrityService.ts @@ -82,7 +82,7 @@ export class IntegrityServiceImpl implements IIntegrityService { private _prompt(): void { const storedData = this._storage.get(); - if (storedData && storedData.dontShowPrompt && storedData.commit === product.commit) { + if (storedData?.dontShowPrompt && storedData.commit === product.commit) { return; // Do not prompt } 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 279a2e8634..8288261017 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 @@ -19,7 +19,6 @@ import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ConfigurationService } from 'vs/platform/configuration/node/configurationService'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; @@ -49,6 +48,7 @@ import { FileUserDataProvider } from 'vs/workbench/services/userData/common/file import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; class TestEnvironmentService extends WorkbenchEnvironmentService { @@ -81,11 +81,12 @@ suite('KeybindingsEditing', () => { instantiationService = new TestInstantiationService(); const environmentService = new TestEnvironmentService(URI.file(testDir)); + + const configService = new TestConfigurationService(); + configService.setUserConfiguration('files', { 'eol': '\n' }); + instantiationService.stub(IEnvironmentService, environmentService); - instantiationService.stub(IConfigurationService, ConfigurationService); - instantiationService.stub(IConfigurationService, 'getValue', { 'eol': '\n' }); - instantiationService.stub(IConfigurationService, 'onDidUpdateConfiguration', () => { }); - instantiationService.stub(IConfigurationService, 'onDidChangeConfiguration', () => { }); + instantiationService.stub(IConfigurationService, configService); instantiationService.stub(IWorkspaceContextService, new TestContextService()); const lifecycleService = new TestLifecycleService(); instantiationService.stub(ILifecycleService, lifecycleService); diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 8cec77e8d3..0f192a8d39 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -31,11 +31,6 @@ export interface IWorkbenchLayoutService extends ILayoutService { _serviceBrand: undefined; - /** - * Emits when the visibility of the title bar changes. - */ - readonly onTitleBarVisibilityChange: Event; - /** * Emits when the zen mode is enabled or disabled. */ @@ -56,6 +51,11 @@ export interface IWorkbenchLayoutService extends ILayoutService { */ readonly onPanelPositionChange: Event; + /** + * Emit when part visibility changes + */ + readonly onPartVisibilityChange: Event; + /** * Asks the part service if all parts have been fully restored. For editor part * this means that the contents of editors have loaded. @@ -70,7 +70,7 @@ export interface IWorkbenchLayoutService extends ILayoutService { /** * Returns the parts HTML element, if there is one. */ - getContainer(part: Parts): HTMLElement; + getContainer(part: Parts): HTMLElement | undefined; /** * Returns if the part is visible. @@ -80,7 +80,7 @@ export interface IWorkbenchLayoutService extends ILayoutService { /** * Returns if the part is visible. */ - getDimension(part: Parts): Dimension; + getDimension(part: Parts): Dimension | undefined; /** * Set activity bar hidden or not diff --git a/src/vs/workbench/services/mode/common/workbenchModeService.ts b/src/vs/workbench/services/mode/common/workbenchModeService.ts index 2cc3f1e141..ade3a29230 100644 --- a/src/vs/workbench/services/mode/common/workbenchModeService.ts +++ b/src/vs/workbench/services/mode/common/workbenchModeService.ts @@ -173,7 +173,7 @@ export class WorkbenchModeServiceImpl extends ModeServiceImpl { mime.clearTextMimes(true /* user configured */); // Register based on settings - if (configuration.files && configuration.files.associations) { + if (configuration.files?.associations) { Object.keys(configuration.files.associations).forEach(pattern => { const langId = configuration.files.associations[pattern]; const mimetype = this.getMimeForMode(langId) || `text/x-${langId}`; diff --git a/src/vs/workbench/services/notification/common/notificationService.ts b/src/vs/workbench/services/notification/common/notificationService.ts index ccbb5ad56b..8e9e698eda 100644 --- a/src/vs/workbench/services/notification/common/notificationService.ts +++ b/src/vs/workbench/services/notification/common/notificationService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { INotificationService, INotification, INotificationHandle, Severity, NotificationMessage, INotificationActions, IPromptChoice, IPromptOptions, IStatusMessageOptions, NoOpNotification, NeverShowAgainScope } from 'vs/platform/notification/common/notification'; +import { INotificationService, INotification, INotificationHandle, Severity, NotificationMessage, INotificationActions, IPromptChoice, IPromptOptions, IStatusMessageOptions, NoOpNotification, NeverShowAgainScope, NotificationsFilter } from 'vs/platform/notification/common/notification'; import { INotificationsModel, NotificationsModel, ChoiceAction } from 'vs/workbench/common/notifications'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; @@ -17,15 +17,16 @@ export class NotificationService extends Disposable implements INotificationServ _serviceBrand: undefined; private _model: INotificationsModel = this._register(new NotificationsModel()); - - get model(): INotificationsModel { - return this._model; - } + get model(): INotificationsModel { return this._model; } constructor(@IStorageService private readonly storageService: IStorageService) { super(); } + setFilter(filter: NotificationsFilter): void { + this._model.setFilter(filter); + } + info(message: NotificationMessage | NotificationMessage[]): void { if (Array.isArray(message)) { message.forEach(m => this.info(m)); @@ -109,7 +110,7 @@ export class NotificationService extends Disposable implements INotificationServ const toDispose = new DisposableStore(); // Handle neverShowAgain option accordingly - if (options && options.neverShowAgain) { + if (options?.neverShowAgain) { const scope = options.neverShowAgain.scope === NeverShowAgainScope.WORKSPACE ? StorageScope.WORKSPACE : StorageScope.GLOBAL; // If the user already picked to not show the notification @@ -162,7 +163,7 @@ export class NotificationService extends Disposable implements INotificationServ // 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 }); + handle = this.notify({ severity, message, actions, sticky: options?.sticky, silent: options?.silent }); Event.once(handle.onDidClose)(() => { diff --git a/src/vs/workbench/services/output/common/outputChannelModel.ts b/src/vs/workbench/services/output/common/outputChannelModel.ts index dc088dcc84..7818fc5227 100644 --- a/src/vs/workbench/services/output/common/outputChannelModel.ts +++ b/src/vs/workbench/services/output/common/outputChannelModel.ts @@ -365,7 +365,7 @@ export class BufferredOutputChannel extends Disposable implements IOutputChannel class BufferedContent { - private static MAX_OUTPUT_LENGTH = 10000 /* Max. number of output lines to show in output */ * 100 /* Guestimated chars per line */; + private static readonly MAX_OUTPUT_LENGTH = 10000 /* Max. number of output lines to show in output */ * 100 /* Guestimated chars per line */; private data: string[] = []; private dataIds: number[] = []; diff --git a/src/vs/workbench/services/panel/common/panelService.ts b/src/vs/workbench/services/panel/common/panelService.ts index c22f2da284..ac5b7d3aea 100644 --- a/src/vs/workbench/services/panel/common/panelService.ts +++ b/src/vs/workbench/services/panel/common/panelService.ts @@ -22,18 +22,18 @@ export interface IPanelService { _serviceBrand: undefined; - readonly onDidPanelOpen: Event<{ panel: IPanel, focus: boolean }>; + readonly onDidPanelOpen: Event<{ readonly panel: IPanel, readonly focus: boolean }>; readonly onDidPanelClose: Event; /** * Opens a panel with the given identifier and pass keyboard focus to it if specified. */ - openPanel(id: string, focus?: boolean): IPanel | null; + openPanel(id: string, focus?: boolean): IPanel | undefined; /** * Returns the current active panel or null if none */ - getActivePanel(): IPanel | null; + getActivePanel(): IPanel | undefined; /** * Returns the panel by id. @@ -43,17 +43,17 @@ export interface IPanelService { /** * Returns all built-in panels following the default order */ - getPanels(): IPanelIdentifier[]; + getPanels(): readonly IPanelIdentifier[]; /** * Returns pinned panels following the visual order */ - getPinnedPanels(): IPanelIdentifier[]; + getPinnedPanels(): readonly IPanelIdentifier[]; /** * Returns the progress indicator for the panel bar. */ - getProgressIndicator(id: string): IProgressIndicator | null; + getProgressIndicator(id: string): IProgressIndicator | undefined; /** * Show an activity in a panel. diff --git a/src/vs/workbench/services/path/common/remotePathService.ts b/src/vs/workbench/services/path/common/remotePathService.ts new file mode 100644 index 0000000000..3054239277 --- /dev/null +++ b/src/vs/workbench/services/path/common/remotePathService.ts @@ -0,0 +1,81 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'vs/base/common/path'; +import * as platform from 'vs/base/common/platform'; +import { URI } from 'vs/base/common/uri'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; + +const REMOTE_PATH_SERVICE_ID = 'remotePath'; +export const IRemotePathService = createDecorator(REMOTE_PATH_SERVICE_ID); + +export interface IRemotePathService { + _serviceBrand: undefined; + + path: Promise; + fileURI(path: string): Promise; +} + +/** + * Provides the correct IPath implementation for dealing with paths that refer to locations in the extension host + */ +export class RemotePathService implements IRemotePathService { + _serviceBrand: undefined; + + private _extHostOS: Promise; + + constructor( + @IRemoteAgentService readonly remoteAgentService: IRemoteAgentService + ) { + this._extHostOS = remoteAgentService.getEnvironment().then(remoteEnvironment => { + return remoteEnvironment ? remoteEnvironment.os : platform.OS; + }); + } + + get path(): Promise { + return this._extHostOS.then(os => { + return os === platform.OperatingSystem.Windows ? + path.win32 : + path.posix; + }); + } + + async fileURI(_path: string): Promise { + let authority = ''; + + // normalize to fwd-slashes on windows, + // on other systems bwd-slashes are valid + // filename character, eg /f\oo/ba\r.txt + if ((await this._extHostOS) === platform.OperatingSystem.Windows) { + _path = _path.replace(/\\/g, '/'); + } + + // check for authority as used in UNC shares + // or use the path as given + if (_path[0] === '/' && _path[1] === '/') { + const idx = _path.indexOf('/', 2); + if (idx === -1) { + authority = _path.substring(2); + _path = '/'; + } else { + authority = _path.substring(2, idx); + _path = _path.substring(idx) || '/'; + } + } + + // return new _URI('file', authority, path, '', ''); + return URI.from({ + scheme: 'file', + authority, + path: _path, + query: '', + fragment: '' + }); + } +} + +registerSingleton(IRemotePathService, RemotePathService, true); diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index aaa710d096..6d433882fc 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -51,11 +51,11 @@ export class PreferencesService extends Disposable implements IPreferencesServic private readonly _onDispose = this._register(new Emitter()); private _defaultUserSettingsUriCounter = 0; - private _defaultUserSettingsContentModel: DefaultSettings; + private _defaultUserSettingsContentModel: DefaultSettings | undefined; private _defaultWorkspaceSettingsUriCounter = 0; - private _defaultWorkspaceSettingsContentModel: DefaultSettings; + private _defaultWorkspaceSettingsContentModel: DefaultSettings | undefined; private _defaultFolderSettingsUriCounter = 0; - private _defaultFolderSettingsContentModel: DefaultSettings; + private _defaultFolderSettingsContentModel: DefaultSettings | undefined; constructor( @IEditorService private readonly editorService: IEditorService, diff --git a/src/vs/workbench/services/preferences/common/keybindingsEditorModel.ts b/src/vs/workbench/services/preferences/common/keybindingsEditorModel.ts index 361cd77e36..d942fc02ac 100644 --- a/src/vs/workbench/services/preferences/common/keybindingsEditorModel.ts +++ b/src/vs/workbench/services/preferences/common/keybindingsEditorModel.ts @@ -80,6 +80,8 @@ export class KeybindingsEditorModel extends EditorModel { @IKeybindingService private readonly keybindingsService: IKeybindingService ) { super(); + this._keybindingItems = []; + this._keybindingItemsSortedByPrecedence = []; this.modifierLabels = { ui: UILabelProvider.modifierLabels[os], aria: AriaLabelProvider.modifierLabels[os], diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 243e085d26..61a6e50628 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -429,7 +429,7 @@ function parse(model: ITextModel, isSettingsProperty: (currentProperty: string, export class WorkspaceConfigurationEditorModel extends SettingsEditorModel { - private _configurationGroups: ISettingsGroup[]; + private _configurationGroups: ISettingsGroup[] = []; get configurationGroups(): ISettingsGroup[] { return this._configurationGroups; @@ -448,9 +448,9 @@ export class WorkspaceConfigurationEditorModel extends SettingsEditorModel { export class DefaultSettings extends Disposable { - private _allSettingsGroups: ISettingsGroup[]; - private _content: string; - private _settingsByName: Map; + private _allSettingsGroups: ISettingsGroup[] | undefined; + private _content: string | undefined; + private _settingsByName = new Map(); readonly _onDidChange: Emitter = this._register(new Emitter()); readonly onDidChange: Event = this._onDidChange.event; @@ -467,7 +467,7 @@ export class DefaultSettings extends Disposable { this.initialize(); } - return this._content; + return this._content!; } getSettingsGroups(forceUpdate = false): ISettingsGroup[] { @@ -475,7 +475,7 @@ export class DefaultSettings extends Disposable { this.initialize(); } - return this._allSettingsGroups; + return this._allSettingsGroups!; } private initialize(): void { @@ -1249,7 +1249,7 @@ export function defaultKeybindingsContents(keybindingService: IKeybindingService export class DefaultKeybindingsEditorModel implements IKeybindingsEditorModel { - private _content: string; + private _content: string | undefined; constructor(private _uri: URI, @IKeybindingService private readonly keybindingService: IKeybindingService) { diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index ff0e0eb81b..cc8f82eac4 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/progressService'; import { localize } from 'vs/nls'; import { IDisposable, dispose, DisposableStore, MutableDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { IProgressService, IProgressOptions, IProgressStep, ProgressLocation, IProgress, Progress, IProgressCompositeOptions, IProgressNotificationOptions, IProgressRunner, IProgressIndicator } from 'vs/platform/progress/common/progress'; +import { IProgressService, IProgressOptions, IProgressStep, ProgressLocation, IProgress, Progress, IProgressCompositeOptions, IProgressNotificationOptions, IProgressRunner, IProgressIndicator, IProgressWindowOptions } from 'vs/platform/progress/common/progress'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { StatusbarAlignment, IStatusbarService } from 'vs/workbench/services/statusbar/common/statusbar'; import { timeout } from 'vs/base/common/async'; @@ -63,7 +63,7 @@ export class ProgressService extends Disposable implements IProgressService { case ProgressLocation.Notification: return this.withNotificationProgress({ ...options, location }, task, onDidCancel); case ProgressLocation.Window: - return this.withWindowProgress(options, task); + return this.withWindowProgress({ ...options, location }, task); case ProgressLocation.Explorer: return this.withViewletProgress('workbench.view.explorer', task, { ...options, location }); case ProgressLocation.Scm: @@ -77,8 +77,8 @@ export class ProgressService extends Disposable implements IProgressService { } } - private withWindowProgress(options: IProgressOptions, callback: (progress: IProgress<{ message?: string }>) => Promise): Promise { - const task: [IProgressOptions, Progress] = [options, new Progress(() => this.updateWindowProgress())]; + private withWindowProgress(options: IProgressWindowOptions, callback: (progress: IProgress<{ message?: string }>) => Promise): Promise { + const task: [IProgressWindowOptions, Progress] = [options, new Progress(() => this.updateWindowProgress())]; const promise = callback(task[1]); @@ -110,6 +110,7 @@ export class ProgressService extends Disposable implements IProgressService { let progressTitle = options.title; let progressMessage = progress.value && progress.value.message; + let progressCommand = (options).command; let text: string; let title: string; @@ -136,7 +137,8 @@ export class ProgressService extends Disposable implements IProgressService { this.globalStatusEntry.value = this.statusbarService.addEntry({ text: `$(sync~spin) ${text}`, - tooltip: title + tooltip: title, + command: progressCommand }, 'status.progress', localize('status.progress', "Progress Message"), StatusbarAlignment.LEFT); } } @@ -308,7 +310,7 @@ export class ProgressService extends Disposable implements IProgressService { return this.withCompositeProgress(this.panelService.getProgressIndicator(panelid), task, options); } - private withCompositeProgress

, R = unknown>(progressIndicator: IProgressIndicator | null, task: (progress: IProgress) => P, options: IProgressCompositeOptions): P { + private withCompositeProgress

, R = unknown>(progressIndicator: IProgressIndicator | undefined, task: (progress: IProgress) => P, options: IProgressCompositeOptions): P { let progressRunner: IProgressRunner | undefined = undefined; const promise = task({ @@ -364,7 +366,7 @@ export class ProgressService extends Disposable implements IProgressService { cancelId: buttons.length - 1, keyEventProcessor: (event: StandardKeyboardEvent) => { const resolved = this.keybindingService.softDispatch(event, this.layoutService.container); - if (resolved && resolved.commandId) { + if (resolved?.commandId) { if (allowableCommands.indexOf(resolved.commandId) === -1) { EventHelper.stop(event, true); } diff --git a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts index fa0b8296ca..a254626f61 100644 --- a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts @@ -21,6 +21,7 @@ import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics import { Emitter } from 'vs/base/common/event'; import { ISignService } from 'vs/platform/sign/common/sign'; import { ILogService } from 'vs/platform/log/common/log'; +import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; export abstract class AbstractRemoteAgentService extends Disposable { @@ -69,6 +70,26 @@ export abstract class AbstractRemoteAgentService extends Disposable { return Promise.resolve(undefined); } + + logTelemetry(eventName: string, data: ITelemetryData): Promise { + const connection = this.getConnection(); + if (connection) { + const client = new RemoteExtensionEnvironmentChannelClient(connection.getChannel('remoteextensionsenvironment')); + return client.logTelemetry(eventName, data); + } + + return Promise.resolve(undefined); + } + + flushTelemetry(): Promise { + const connection = this.getConnection(); + if (connection) { + const client = new RemoteExtensionEnvironmentChannelClient(connection.getChannel('remoteextensionsenvironment')); + return client.flushTelemetry(); + } + + return Promise.resolve(undefined); + } } export class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection { diff --git a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts index 9b54131673..ace208fd42 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts @@ -10,6 +10,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { RemoteAuthorities } from 'vs/base/common/network'; +import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; export interface IGetEnvironmentDataArguments { language: string; @@ -70,4 +71,12 @@ export class RemoteExtensionEnvironmentChannelClient { disableTelemetry(): Promise { return this.channel.call('disableTelemetry'); } + + logTelemetry(eventName: string, data: ITelemetryData): Promise { + return this.channel.call('logTelemetry', { eventName, data }); + } + + flushTelemetry(): Promise { + return this.channel.call('flushTelemetry'); + } } diff --git a/src/vs/workbench/services/remote/common/remoteAgentService.ts b/src/vs/workbench/services/remote/common/remoteAgentService.ts index dba56ecae4..d54857e522 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentService.ts @@ -9,6 +9,7 @@ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { Event } from 'vs/base/common/event'; import { PersistenConnectionEvent as PersistentConnectionEvent, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; +import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; export const RemoteExtensionLogFileName = 'remoteagent'; @@ -23,6 +24,8 @@ export interface IRemoteAgentService { getEnvironment(bail?: boolean): Promise; getDiagnosticInfo(options: IDiagnosticInfoOptions): Promise; disableTelemetry(): Promise; + logTelemetry(eventName: string, data?: ITelemetryData): Promise; + flushTelemetry(): Promise; } export interface IRemoteAgentConnection { diff --git a/src/vs/workbench/services/remote/node/tunnelService.ts b/src/vs/workbench/services/remote/node/tunnelService.ts index 1f08606179..6a905d07b4 100644 --- a/src/vs/workbench/services/remote/node/tunnelService.ts +++ b/src/vs/workbench/services/remote/node/tunnelService.ts @@ -26,7 +26,7 @@ export async function createRemoteTunnel(options: IConnectionOptions, tunnelRemo class NodeRemoteTunnel extends Disposable implements RemoteTunnel { public readonly tunnelRemotePort: number; - public tunnelLocalPort: number; + public tunnelLocalPort!: number; private readonly _options: IConnectionOptions; private readonly _server: net.Server; diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 7a6f63299c..3bf1d4f0ec 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -17,6 +17,7 @@ import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { Event } from 'vs/base/common/event'; import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; +import { relative } from 'vs/base/common/path'; export const VIEWLET_ID = 'workbench.view.search'; export const PANEL_ID = 'workbench.view.search'; @@ -371,7 +372,8 @@ export function pathIncludedInQuery(queryProps: ICommonQueryProps, fsPath: return !!queryProps.folderQueries && queryProps.folderQueries.every(fq => { const searchPath = fq.folder.fsPath; if (extpath.isEqualOrParent(fsPath, searchPath)) { - return !fq.includePattern || !!glob.match(fq.includePattern, fsPath); + const relPath = relative(searchPath, fsPath); + return !fq.includePattern || !!glob.match(fq.includePattern, relPath); } else { return false; } diff --git a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts index d54eb867e2..2fa1aa7c29 100644 --- a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts +++ b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts @@ -425,13 +425,14 @@ function getRgArgs(query: TextSearchQuery, options: TextSearchOptions): string[] args.push('--pcre2'); } - if (query.isRegExp) { - query.pattern = unicodeEscapesToPCRE2(query.pattern); - } - // Allow $ to match /r/n args.push('--crlf'); + if (query.isRegExp) { + query.pattern = unicodeEscapesToPCRE2(query.pattern); + args.push('--auto-hybrid-regex'); + } + let searchPatternAfterDoubleDashes: Maybe; if (query.isWordMatch) { const regexp = createRegExp(query.pattern, !!query.isRegExp, { wholeWord: query.isWordMatch }); @@ -441,7 +442,6 @@ function getRgArgs(query: TextSearchQuery, options: TextSearchOptions): string[] let fixedRegexpQuery = fixRegexNewline(query.pattern); fixedRegexpQuery = fixNewline(fixedRegexpQuery); args.push('--regexp', fixedRegexpQuery); - args.push('--auto-hybrid-regex'); } else { searchPatternAfterDoubleDashes = query.pattern; args.push('--fixed-strings'); diff --git a/src/vs/workbench/services/telemetry/browser/telemetryService.ts b/src/vs/workbench/services/telemetry/browser/telemetryService.ts index 9d0c92ab2d..cb1178a21f 100644 --- a/src/vs/workbench/services/telemetry/browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/browser/telemetryService.ts @@ -15,55 +15,26 @@ import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/pla import { IStorageService } from 'vs/platform/storage/common/storage'; import { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/browser/workbenchCommonProperties'; import { IProductService } from 'vs/platform/product/common/productService'; -import { ApplicationInsights } from '@microsoft/applicationinsights-web'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; export class WebTelemetryAppender implements ITelemetryAppender { - private _aiClient?: ApplicationInsights; - constructor(aiKey: string, private _logService: ILogService - , @IWorkbenchEnvironmentService private _environmentService: IWorkbenchEnvironmentService) { // {{ SQL CARBON EDIT }} - const initConfig = { - config: { - instrumentationKey: aiKey, - endpointUrl: 'https://vortex.data.microsoft.com/collect/v1', - emitLineDelimitedJson: true, - autoTrackPageVisitTime: false, - disableExceptionTracking: true, - disableAjaxTracking: true - } - }; - - this._aiClient = new ApplicationInsights(initConfig); - this._aiClient.loadAppInsights(); - } + constructor(private _logService: ILogService, private _appender: IRemoteAgentService, + @IWorkbenchEnvironmentService private _environmentService: IWorkbenchEnvironmentService) { } // {{ SQL CARBON EDIT }} log(eventName: string, data: any): void { - if (!this._aiClient) { - return; - } - data = validateTelemetryData(data); this._logService.trace(`telemetry/${eventName}`, data); - // {{ SQL CARBON EDIT }} - const eventPrefix = this._environmentService.appQuality !== 'stable' ? 'adsworkbench/' : 'monacoworkbench/'; - this._aiClient.trackEvent({ - name: eventPrefix + eventName, + const eventPrefix = this._environmentService.appQuality !== 'stable' ? '/adsworkbench/' : '/monacoworkbench/'; // {{SQL CARBON EDIT}} + this._appender.logTelemetry(eventPrefix + eventName, { properties: data.properties, measurements: data.measurements }); } flush(): Promise { - if (this._aiClient) { - return new Promise(resolve => { - this._aiClient!.flush(); - this._aiClient = undefined; - resolve(undefined); - }); - } - - return Promise.resolve(); + return this._appender.flushTelemetry(); } } @@ -78,14 +49,14 @@ export class TelemetryService extends Disposable implements ITelemetryService { @ILogService logService: ILogService, @IConfigurationService configurationService: IConfigurationService, @IStorageService storageService: IStorageService, - @IProductService productService: IProductService + @IProductService productService: IProductService, + @IRemoteAgentService remoteAgentService: IRemoteAgentService ) { super(); - const aiKey = productService.aiConfig && productService.aiConfig.asimovKey; - if (!environmentService.isExtensionDevelopment && !environmentService.args['disable-telemetry'] && !!productService.enableTelemetry && !!aiKey) { + if (!environmentService.args['disable-telemetry'] && !!productService.enableTelemetry) { const config: ITelemetryServiceConfig = { - appender: combinedAppender(new WebTelemetryAppender(aiKey, logService, environmentService), new LogAppender(logService)), + appender: combinedAppender(new WebTelemetryAppender(logService, remoteAgentService, environmentService), new LogAppender(logService)), // {{SQL CARBON EDIT}} commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId, environmentService.configuration.remoteAuthority), piiPaths: [environmentService.appRoot] }; diff --git a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts index 32b05d5570..49781cbd4e 100644 --- a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts @@ -38,7 +38,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, productService.msftInternalDomains, environmentService.installSourcePath, environmentService.configuration.remoteAuthority, environmentService.options && environmentService.options.resolveCommonTelemetryProperties), + commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId, productService.msftInternalDomains, environmentService.installSourcePath, environmentService.configuration.remoteAuthority), piiPaths: environmentService.extensionsPath ? [environmentService.appRoot, environmentService.extensionsPath] : [environmentService.appRoot] }; diff --git a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts index f1a03783c5..e040466be4 100644 --- a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts +++ b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts @@ -44,7 +44,7 @@ export class BrowserTextFileService extends AbstractTextFileService { if (this.fileService.canHandleResource(dirtyResource)) { const model = this.models.get(dirtyResource); - hasBackup = !!(model && model.hasBackup()); + hasBackup = !!(model?.hasBackup()); } else if (dirtyResource.scheme === Schemas.untitled) { hasBackup = this.untitledEditorService.hasBackup(dirtyResource); } diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 08a40efe1a..4a75134f4e 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -93,7 +93,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex this.autoSaveContext = AutoSaveContext.bindTo(contextKeyService); const configuration = configurationService.getValue(); - this.currentFilesAssociationConfig = configuration && configuration.files && configuration.files.associations; + this.currentFilesAssociationConfig = configuration?.files?.associations; this.onFilesConfigurationChange(configuration); @@ -298,11 +298,11 @@ export abstract class AbstractTextFileService extends Disposable implements ITex protected onFilesConfigurationChange(configuration: IFilesConfiguration): void { const wasAutoSaveEnabled = (this.getAutoSaveMode() !== AutoSaveMode.OFF); - const autoSaveMode = (configuration && configuration.files && configuration.files.autoSave) || AutoSaveConfiguration.OFF; + const autoSaveMode = configuration?.files?.autoSave || AutoSaveConfiguration.OFF; this.autoSaveContext.set(autoSaveMode); switch (autoSaveMode) { case AutoSaveConfiguration.AFTER_DELAY: - this.configuredAutoSaveDelay = configuration && configuration.files && configuration.files.autoSaveDelay; + this.configuredAutoSaveDelay = configuration?.files?.autoSaveDelay; this.configuredAutoSaveOnFocusChange = false; this.configuredAutoSaveOnWindowChange = false; break; @@ -335,14 +335,14 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } // Check for change in files associations - const filesAssociation = configuration && configuration.files && configuration.files.associations; + const filesAssociation = configuration?.files?.associations; if (!objects.equals(this.currentFilesAssociationConfig, filesAssociation)) { this.currentFilesAssociationConfig = filesAssociation; this._onFilesAssociationChange.fire(); } // Hot exit - const hotExitMode = configuration && configuration.files && configuration.files.hotExit; + const hotExitMode = configuration?.files?.hotExit; if (hotExitMode === HotExitConfiguration.OFF || hotExitMode === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { this.configuredHotExit = hotExitMode; } else { @@ -389,7 +389,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return { ...stream, encoding: 'utf8', - value: await createTextBufferFactoryFromStream(stream.value, undefined, options && options.acceptTextOnly ? throwOnBinary : undefined) + value: await createTextBufferFactoryFromStream(stream.value, undefined, options?.acceptTextOnly ? throwOnBinary : undefined) }; } @@ -549,7 +549,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex async save(resource: URI, options?: ISaveOptions): Promise { // Run a forced save if we detect the file is not dirty so that save participants can still run - if (options && options.force && this.fileService.canHandleResource(resource) && !this.isDirty(resource)) { + if (options?.force && this.fileService.canHandleResource(resource) && !this.isDirty(resource)) { const model = this._models.get(resource); if (model) { options.reason = SaveReason.EXPLICIT; @@ -835,7 +835,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Prefer an existing model if it is already loaded for the given target resource let targetExists: boolean = false; let targetModel = this.models.get(target); - if (targetModel && targetModel.isResolved()) { + if (targetModel?.isResolved()) { targetExists = true; } @@ -938,7 +938,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } private async doRevertAllFiles(resources?: URI[], options?: IRevertOptions): Promise { - const fileModels = options && options.force ? this.getFileModels(resources) : this.getDirtyFileModels(resources); + const fileModels = options?.force ? this.getFileModels(resources) : this.getDirtyFileModels(resources); const mapResourceToResult = new ResourceMap(); fileModels.forEach(m => { @@ -949,7 +949,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex await Promise.all(fileModels.map(async model => { try { - await model.revert(options && options.soft); + await model.revert(options?.soft); if (!model.isDirty()) { const result = mapResourceToResult.get(model.getResource()); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 203403bb27..c4191ed4ed 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -8,7 +8,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { guessMimeTypes } from 'vs/base/common/mime'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { URI } from 'vs/base/common/uri'; -import { isUndefinedOrNull } from 'vs/base/common/types'; +import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ITextFileService, IAutoSaveConfiguration, ModelState, ITextFileEditorModel, ISaveOptions, ISaveErrorHandler, ISaveParticipant, StateChange, SaveReason, ITextFileStreamContent, ILoadOptions, LoadReason, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; @@ -75,40 +75,34 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private readonly _onDidStateChange: Emitter = this._register(new Emitter()); readonly onDidStateChange: Event = this._onDidStateChange.event; - private resource: URI; + private contentEncoding: string | undefined; // encoding as reported from disk - private contentEncoding: string; // encoding as reported from disk - private preferredEncoding: string | undefined; // encoding as chosen by the user + private versionId = 0; + private bufferSavedVersionId: number | undefined; + private blockModelContentChange = false; - private preferredMode: string | undefined; // mode as chosen by the user + private lastResolvedFileStat: IFileStatWithMetadata | undefined; - private versionId: number; - private bufferSavedVersionId: number; - private blockModelContentChange: boolean; - - private lastResolvedFileStat: IFileStatWithMetadata; - - private autoSaveAfterMillies?: number; - private autoSaveAfterMilliesEnabled: boolean; + private autoSaveAfterMillies: number | undefined; + private autoSaveAfterMilliesEnabled: boolean | undefined; private readonly autoSaveDisposable = this._register(new MutableDisposable()); - private saveSequentializer: SaveSequentializer; - private lastSaveAttemptTime: number; + private readonly saveSequentializer = new SaveSequentializer(); + private lastSaveAttemptTime = 0; - private contentChangeEventScheduler: RunOnceScheduler; - private orphanedChangeEventScheduler: RunOnceScheduler; + private readonly contentChangeEventScheduler = this._register(new RunOnceScheduler(() => this._onDidContentChange.fire(StateChange.CONTENT_CHANGE), TextFileEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY)); + private readonly orphanedChangeEventScheduler = this._register(new RunOnceScheduler(() => this._onDidStateChange.fire(StateChange.ORPHANED_CHANGE), TextFileEditorModel.DEFAULT_ORPHANED_CHANGE_BUFFER_DELAY)); - private dirty: boolean; - private inConflictMode: boolean; - private inOrphanMode: boolean; - private inErrorMode: boolean; - - private disposed: boolean; + private dirty = false; + private inConflictMode = false; + private inOrphanMode = false; + private inErrorMode = false; + private disposed = false; constructor( - resource: URI, - preferredEncoding: string | undefined, - preferredMode: string | undefined, + private resource: URI, + private preferredEncoding: string | undefined, // encoding as chosen by the user + private preferredMode: string | undefined, // mode as chosen by the user @INotificationService private readonly notificationService: INotificationService, @IModeService modeService: IModeService, @IModelService modelService: IModelService, @@ -123,18 +117,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil ) { super(modelService, modeService); - this.resource = resource; - this.preferredEncoding = preferredEncoding; - this.preferredMode = preferredMode; - this.inOrphanMode = false; - this.dirty = false; - this.versionId = 0; - this.lastSaveAttemptTime = 0; - this.saveSequentializer = new SaveSequentializer(); - - this.contentChangeEventScheduler = this._register(new RunOnceScheduler(() => this._onDidContentChange.fire(StateChange.CONTENT_CHANGE), TextFileEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY)); - this.orphanedChangeEventScheduler = this._register(new RunOnceScheduler(() => this._onDidStateChange.fire(StateChange.ORPHANED_CHANGE), TextFileEditorModel.DEFAULT_ORPHANED_CHANGE_BUFFER_DELAY)); - this.updateAutoSaveConfiguration(textFileService.getAutoSaveConfiguration()); this.registerListeners(); @@ -347,8 +329,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } private async loadFromFile(options?: ILoadOptions): Promise { - const forceReadFromDisk = options && options.forceReadFromDisk; - const allowBinary = this.isResolved() /* always allow if we resolved previously */ || (options && options.allowBinary); + const forceReadFromDisk = options?.forceReadFromDisk; + const allowBinary = this.isResolved() /* always allow if we resolved previously */ || options?.allowBinary; // Decide on etag let etag: string | undefined; @@ -454,7 +436,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } else { type FileGetClassification = {} & FileTelemetryDataFragment; - this.telemetryService.publicLog2('fileGet', this.getTelemetryData(options && options.reason ? options.reason : LoadReason.OTHER)); + this.telemetryService.publicLog2('fileGet', this.getTelemetryData(options?.reason ?? LoadReason.OTHER)); } return this; @@ -727,12 +709,13 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Save to Disk // mark the save operation as currently pending with the versionId (it might have changed from a save participant triggering) this.logService.trace(`doSave(${versionId}) - before write()`, this.resource); - return this.saveSequentializer.setPending(newVersionId, this.textFileService.write(this.lastResolvedFileStat.resource, this.createSnapshot(), { + const lastResolvedFileStat = assertIsDefined(this.lastResolvedFileStat); + return this.saveSequentializer.setPending(newVersionId, this.textFileService.write(lastResolvedFileStat.resource, this.createSnapshot(), { overwriteReadonly: options.overwriteReadonly, overwriteEncoding: options.overwriteEncoding, - mtime: this.lastResolvedFileStat.mtime, + mtime: lastResolvedFileStat.mtime, encoding: this.getEncoding(), - etag: this.lastResolvedFileStat.etag, + etag: lastResolvedFileStat.etag, writeElevated: options.writeElevated }).then(stat => { this.logService.trace(`doSave(${versionId}) - after write()`, this.resource); @@ -800,11 +783,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return 'keybindings'; } - // Check for locale file - if (isEqual(this.resource, joinPath(this.environmentService.userRoamingDataHome, 'locale.json'))) { - return 'locale'; - } - // Check for snippets if (isEqualOrParent(this.resource, joinPath(this.environmentService.userRoamingDataHome, 'snippets'))) { return 'snippets'; @@ -850,10 +828,11 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return Promise.resolve(); } - return this.saveSequentializer.setPending(versionId, this.textFileService.write(this.lastResolvedFileStat.resource, this.createSnapshot(), { - mtime: this.lastResolvedFileStat.mtime, + const lastResolvedFileStat = assertIsDefined(this.lastResolvedFileStat); + return this.saveSequentializer.setPending(versionId, this.textFileService.write(lastResolvedFileStat.resource, this.createSnapshot(), { + mtime: lastResolvedFileStat.mtime, encoding: this.getEncoding(), - etag: this.lastResolvedFileStat.etag + etag: lastResolvedFileStat.etag }).then(stat => { // Updated resolved stat with updated stat since touching it might have changed mtime @@ -953,7 +932,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } - getEncoding(): string { + getEncoding(): string | undefined { return this.preferredEncoding || this.contentEncoding; } @@ -994,7 +973,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } - updatePreferredEncoding(encoding: string): void { + updatePreferredEncoding(encoding: string | undefined): void { if (!this.isNewEncoding(encoding)) { return; } @@ -1005,7 +984,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this._onDidStateChange.fire(StateChange.ENCODING); } - private isNewEncoding(encoding: string): boolean { + private isNewEncoding(encoding: string | undefined): boolean { if (this.preferredEncoding === encoding) { return false; // return early if the encoding is already the same } @@ -1033,7 +1012,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this.resource; } - getStat(): IFileStatWithMetadata { + getStat(): IFileStatWithMetadata | undefined { return this.lastResolvedFileStat; } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index bf40906b1a..669f85a6c7 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -38,7 +38,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE private readonly _onModelOrphanedChanged: Emitter = this._register(new Emitter()); readonly onModelOrphanedChanged: Event = this._onModelOrphanedChanged.event; - private _onModelsDirty!: Event; + private _onModelsDirty: Event | undefined; get onModelsDirty(): Event { if (!this._onModelsDirty) { this._onModelsDirty = this.debounce(this.onModelDirty); @@ -47,7 +47,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return this._onModelsDirty; } - private _onModelsSaveError!: Event; + private _onModelsSaveError: Event | undefined; get onModelsSaveError(): Event { if (!this._onModelsSaveError) { this._onModelsSaveError = this.debounce(this.onModelSaveError); @@ -56,7 +56,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return this._onModelsSaveError; } - private _onModelsSaved!: Event; + private _onModelsSaved: Event | undefined; get onModelsSaved(): Event { if (!this._onModelsSaved) { this._onModelsSaved = this.debounce(this.onModelSaved); @@ -65,7 +65,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return this._onModelsSaved; } - private _onModelsReverted!: Event; + private _onModelsReverted: Event | undefined; get onModelsReverted(): Event { if (!this._onModelsReverted) { this._onModelsReverted = this.debounce(this.onModelReverted); @@ -127,7 +127,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // Model exists let model = this.get(resource); if (model) { - if (options && options.reload) { + if (options?.reload) { // async reload: trigger a reload but return immediately if (options.reload.async) { @@ -198,7 +198,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE this.mapResourceToPendingModelLoaders.delete(resource); // Apply mode if provided - if (options && options.mode) { + if (options?.mode) { resolvedModel.setMode(options.mode); } diff --git a/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts b/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts index ef0a5ed015..141a78b898 100644 --- a/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts +++ b/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts @@ -30,9 +30,9 @@ export class TextResourcePropertiesService implements ITextResourcePropertiesSer } getEOL(resource?: URI, language?: string): string { - const filesConfiguration = this.configurationService.getValue<{ eol: string }>('files', { overrideIdentifier: language, resource }); - if (filesConfiguration && filesConfiguration.eol && filesConfiguration.eol !== 'auto') { - return filesConfiguration.eol; + const eol = this.configurationService.getValue('files.eol', { overrideIdentifier: language, resource }); + if (eol && eol !== 'auto') { + return eol; } const os = this.getOS(resource); return os === OperatingSystem.Linux || os === OperatingSystem.Macintosh ? '\n' : '\r\n'; diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 2518c5f5e9..348721f868 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -14,6 +14,7 @@ import { ITextBufferFactory, ITextModel, ITextSnapshot } from 'vs/editor/common/ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; import { isUndefinedOrNull } from 'vs/base/common/types'; +import { isNative } from 'vs/base/common/platform'; export const ITextFileService = createDecorator('textFileService'); @@ -463,7 +464,7 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport hasState(state: ModelState): boolean; - updatePreferredEncoding(encoding: string): void; + updatePreferredEncoding(encoding: string | undefined): void; save(options?: ISaveOptions): Promise; @@ -573,243 +574,257 @@ export function toBufferOrReadable(value: string | ITextSnapshot | undefined): V return new TextSnapshotReadable(value); } -export const SUPPORTED_ENCODINGS: { [encoding: string]: { labelLong: string; labelShort: string; order: number; encodeOnly?: boolean; alias?: string } } = { - utf8: { - labelLong: 'UTF-8', - labelShort: 'UTF-8', - order: 1, - alias: 'utf8bom' - }, - utf8bom: { - labelLong: 'UTF-8 with BOM', - labelShort: 'UTF-8 with BOM', - encodeOnly: true, - order: 2, - alias: 'utf8' - }, - utf16le: { - labelLong: 'UTF-16 LE', - labelShort: 'UTF-16 LE', - order: 3 - }, - utf16be: { - labelLong: 'UTF-16 BE', - labelShort: 'UTF-16 BE', - order: 4 - }, - windows1252: { - labelLong: 'Western (Windows 1252)', - labelShort: 'Windows 1252', - order: 5 - }, - iso88591: { - labelLong: 'Western (ISO 8859-1)', - labelShort: 'ISO 8859-1', - order: 6 - }, - iso88593: { - labelLong: 'Western (ISO 8859-3)', - labelShort: 'ISO 8859-3', - order: 7 - }, - iso885915: { - labelLong: 'Western (ISO 8859-15)', - labelShort: 'ISO 8859-15', - order: 8 - }, - macroman: { - labelLong: 'Western (Mac Roman)', - labelShort: 'Mac Roman', - order: 9 - }, - cp437: { - labelLong: 'DOS (CP 437)', - labelShort: 'CP437', - order: 10 - }, - windows1256: { - labelLong: 'Arabic (Windows 1256)', - labelShort: 'Windows 1256', - order: 11 - }, - iso88596: { - labelLong: 'Arabic (ISO 8859-6)', - labelShort: 'ISO 8859-6', - order: 12 - }, - windows1257: { - labelLong: 'Baltic (Windows 1257)', - labelShort: 'Windows 1257', - order: 13 - }, - iso88594: { - labelLong: 'Baltic (ISO 8859-4)', - labelShort: 'ISO 8859-4', - order: 14 - }, - iso885914: { - labelLong: 'Celtic (ISO 8859-14)', - labelShort: 'ISO 8859-14', - order: 15 - }, - windows1250: { - labelLong: 'Central European (Windows 1250)', - labelShort: 'Windows 1250', - order: 16 - }, - iso88592: { - labelLong: 'Central European (ISO 8859-2)', - labelShort: 'ISO 8859-2', - order: 17 - }, - cp852: { - labelLong: 'Central European (CP 852)', - labelShort: 'CP 852', - order: 18 - }, - windows1251: { - labelLong: 'Cyrillic (Windows 1251)', - labelShort: 'Windows 1251', - order: 19 - }, - cp866: { - labelLong: 'Cyrillic (CP 866)', - labelShort: 'CP 866', - order: 20 - }, - iso88595: { - labelLong: 'Cyrillic (ISO 8859-5)', - labelShort: 'ISO 8859-5', - order: 21 - }, - koi8r: { - labelLong: 'Cyrillic (KOI8-R)', - labelShort: 'KOI8-R', - order: 22 - }, - koi8u: { - labelLong: 'Cyrillic (KOI8-U)', - labelShort: 'KOI8-U', - order: 23 - }, - iso885913: { - labelLong: 'Estonian (ISO 8859-13)', - labelShort: 'ISO 8859-13', - order: 24 - }, - windows1253: { - labelLong: 'Greek (Windows 1253)', - labelShort: 'Windows 1253', - order: 25 - }, - iso88597: { - labelLong: 'Greek (ISO 8859-7)', - labelShort: 'ISO 8859-7', - order: 26 - }, - windows1255: { - labelLong: 'Hebrew (Windows 1255)', - labelShort: 'Windows 1255', - order: 27 - }, - iso88598: { - labelLong: 'Hebrew (ISO 8859-8)', - labelShort: 'ISO 8859-8', - order: 28 - }, - iso885910: { - labelLong: 'Nordic (ISO 8859-10)', - labelShort: 'ISO 8859-10', - order: 29 - }, - iso885916: { - labelLong: 'Romanian (ISO 8859-16)', - labelShort: 'ISO 8859-16', - order: 30 - }, - windows1254: { - labelLong: 'Turkish (Windows 1254)', - labelShort: 'Windows 1254', - order: 31 - }, - iso88599: { - labelLong: 'Turkish (ISO 8859-9)', - labelShort: 'ISO 8859-9', - order: 32 - }, - windows1258: { - labelLong: 'Vietnamese (Windows 1258)', - labelShort: 'Windows 1258', - order: 33 - }, - gbk: { - labelLong: 'Simplified Chinese (GBK)', - labelShort: 'GBK', - order: 34 - }, - gb18030: { - labelLong: 'Simplified Chinese (GB18030)', - labelShort: 'GB18030', - order: 35 - }, - cp950: { - labelLong: 'Traditional Chinese (Big5)', - labelShort: 'Big5', - order: 36 - }, - big5hkscs: { - labelLong: 'Traditional Chinese (Big5-HKSCS)', - labelShort: 'Big5-HKSCS', - order: 37 - }, - shiftjis: { - labelLong: 'Japanese (Shift JIS)', - labelShort: 'Shift JIS', - order: 38 - }, - eucjp: { - labelLong: 'Japanese (EUC-JP)', - labelShort: 'EUC-JP', - order: 39 - }, - euckr: { - labelLong: 'Korean (EUC-KR)', - labelShort: 'EUC-KR', - order: 40 - }, - windows874: { - labelLong: 'Thai (Windows 874)', - labelShort: 'Windows 874', - order: 41 - }, - iso885911: { - labelLong: 'Latin/Thai (ISO 8859-11)', - labelShort: 'ISO 8859-11', - order: 42 - }, - koi8ru: { - labelLong: 'Cyrillic (KOI8-RU)', - labelShort: 'KOI8-RU', - order: 43 - }, - koi8t: { - labelLong: 'Tajik (KOI8-T)', - labelShort: 'KOI8-T', - order: 44 - }, - gb2312: { - labelLong: 'Simplified Chinese (GB 2312)', - labelShort: 'GB 2312', - order: 45 - }, - cp865: { - labelLong: 'Nordic DOS (CP 865)', - labelShort: 'CP 865', - order: 46 - }, - cp850: { - labelLong: 'Western European DOS (CP 850)', - labelShort: 'CP 850', - order: 47 - } -}; +export const SUPPORTED_ENCODINGS: { [encoding: string]: { labelLong: string; labelShort: string; order: number; encodeOnly?: boolean; alias?: string } } = + + // Desktop + isNative ? + { + utf8: { + labelLong: 'UTF-8', + labelShort: 'UTF-8', + order: 1, + alias: 'utf8bom' + }, + utf8bom: { + labelLong: 'UTF-8 with BOM', + labelShort: 'UTF-8 with BOM', + encodeOnly: true, + order: 2, + alias: 'utf8' + }, + utf16le: { + labelLong: 'UTF-16 LE', + labelShort: 'UTF-16 LE', + order: 3 + }, + utf16be: { + labelLong: 'UTF-16 BE', + labelShort: 'UTF-16 BE', + order: 4 + }, + windows1252: { + labelLong: 'Western (Windows 1252)', + labelShort: 'Windows 1252', + order: 5 + }, + iso88591: { + labelLong: 'Western (ISO 8859-1)', + labelShort: 'ISO 8859-1', + order: 6 + }, + iso88593: { + labelLong: 'Western (ISO 8859-3)', + labelShort: 'ISO 8859-3', + order: 7 + }, + iso885915: { + labelLong: 'Western (ISO 8859-15)', + labelShort: 'ISO 8859-15', + order: 8 + }, + macroman: { + labelLong: 'Western (Mac Roman)', + labelShort: 'Mac Roman', + order: 9 + }, + cp437: { + labelLong: 'DOS (CP 437)', + labelShort: 'CP437', + order: 10 + }, + windows1256: { + labelLong: 'Arabic (Windows 1256)', + labelShort: 'Windows 1256', + order: 11 + }, + iso88596: { + labelLong: 'Arabic (ISO 8859-6)', + labelShort: 'ISO 8859-6', + order: 12 + }, + windows1257: { + labelLong: 'Baltic (Windows 1257)', + labelShort: 'Windows 1257', + order: 13 + }, + iso88594: { + labelLong: 'Baltic (ISO 8859-4)', + labelShort: 'ISO 8859-4', + order: 14 + }, + iso885914: { + labelLong: 'Celtic (ISO 8859-14)', + labelShort: 'ISO 8859-14', + order: 15 + }, + windows1250: { + labelLong: 'Central European (Windows 1250)', + labelShort: 'Windows 1250', + order: 16 + }, + iso88592: { + labelLong: 'Central European (ISO 8859-2)', + labelShort: 'ISO 8859-2', + order: 17 + }, + cp852: { + labelLong: 'Central European (CP 852)', + labelShort: 'CP 852', + order: 18 + }, + windows1251: { + labelLong: 'Cyrillic (Windows 1251)', + labelShort: 'Windows 1251', + order: 19 + }, + cp866: { + labelLong: 'Cyrillic (CP 866)', + labelShort: 'CP 866', + order: 20 + }, + iso88595: { + labelLong: 'Cyrillic (ISO 8859-5)', + labelShort: 'ISO 8859-5', + order: 21 + }, + koi8r: { + labelLong: 'Cyrillic (KOI8-R)', + labelShort: 'KOI8-R', + order: 22 + }, + koi8u: { + labelLong: 'Cyrillic (KOI8-U)', + labelShort: 'KOI8-U', + order: 23 + }, + iso885913: { + labelLong: 'Estonian (ISO 8859-13)', + labelShort: 'ISO 8859-13', + order: 24 + }, + windows1253: { + labelLong: 'Greek (Windows 1253)', + labelShort: 'Windows 1253', + order: 25 + }, + iso88597: { + labelLong: 'Greek (ISO 8859-7)', + labelShort: 'ISO 8859-7', + order: 26 + }, + windows1255: { + labelLong: 'Hebrew (Windows 1255)', + labelShort: 'Windows 1255', + order: 27 + }, + iso88598: { + labelLong: 'Hebrew (ISO 8859-8)', + labelShort: 'ISO 8859-8', + order: 28 + }, + iso885910: { + labelLong: 'Nordic (ISO 8859-10)', + labelShort: 'ISO 8859-10', + order: 29 + }, + iso885916: { + labelLong: 'Romanian (ISO 8859-16)', + labelShort: 'ISO 8859-16', + order: 30 + }, + windows1254: { + labelLong: 'Turkish (Windows 1254)', + labelShort: 'Windows 1254', + order: 31 + }, + iso88599: { + labelLong: 'Turkish (ISO 8859-9)', + labelShort: 'ISO 8859-9', + order: 32 + }, + windows1258: { + labelLong: 'Vietnamese (Windows 1258)', + labelShort: 'Windows 1258', + order: 33 + }, + gbk: { + labelLong: 'Simplified Chinese (GBK)', + labelShort: 'GBK', + order: 34 + }, + gb18030: { + labelLong: 'Simplified Chinese (GB18030)', + labelShort: 'GB18030', + order: 35 + }, + cp950: { + labelLong: 'Traditional Chinese (Big5)', + labelShort: 'Big5', + order: 36 + }, + big5hkscs: { + labelLong: 'Traditional Chinese (Big5-HKSCS)', + labelShort: 'Big5-HKSCS', + order: 37 + }, + shiftjis: { + labelLong: 'Japanese (Shift JIS)', + labelShort: 'Shift JIS', + order: 38 + }, + eucjp: { + labelLong: 'Japanese (EUC-JP)', + labelShort: 'EUC-JP', + order: 39 + }, + euckr: { + labelLong: 'Korean (EUC-KR)', + labelShort: 'EUC-KR', + order: 40 + }, + windows874: { + labelLong: 'Thai (Windows 874)', + labelShort: 'Windows 874', + order: 41 + }, + iso885911: { + labelLong: 'Latin/Thai (ISO 8859-11)', + labelShort: 'ISO 8859-11', + order: 42 + }, + koi8ru: { + labelLong: 'Cyrillic (KOI8-RU)', + labelShort: 'KOI8-RU', + order: 43 + }, + koi8t: { + labelLong: 'Tajik (KOI8-T)', + labelShort: 'KOI8-T', + order: 44 + }, + gb2312: { + labelLong: 'Simplified Chinese (GB 2312)', + labelShort: 'GB 2312', + order: 45 + }, + cp865: { + labelLong: 'Nordic DOS (CP 865)', + labelShort: 'CP 865', + order: 46 + }, + cp850: { + labelLong: 'Western European DOS (CP 850)', + labelShort: 'CP 850', + order: 47 + } + } : + + // Web (https://github.com/microsoft/vscode/issues/79275) + { + utf8: { + labelLong: 'UTF-8', + labelShort: 'UTF-8', + order: 1, + alias: 'utf8bom' + } + }; diff --git a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index 716cd81100..d063ddf29b 100644 --- a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -67,7 +67,7 @@ export class NativeTextFileService extends AbstractTextFileService { super(contextService, fileService, untitledEditorService, lifecycleService, instantiationService, configurationService, modeService, modelService, environmentService, notificationService, backupFileService, historyService, contextKeyService, dialogService, fileDialogService, editorService, textResourceConfigurationService); } - private _encoding!: EncodingOracle; + private _encoding: EncodingOracle | undefined; get encoding(): EncodingOracle { if (!this._encoding) { this._encoding = this._register(this.instantiationService.createInstance(EncodingOracle)); @@ -106,12 +106,12 @@ export class NativeTextFileService extends AbstractTextFileService { // read through encoding library const decoder = await toDecodeStream(streamToNodeReadable(bufferStream.value), { - guessEncoding: (options && options.autoGuessEncoding) || this.textResourceConfigurationService.getValue(resource, 'files.autoGuessEncoding'), + guessEncoding: options?.autoGuessEncoding || this.textResourceConfigurationService.getValue(resource, 'files.autoGuessEncoding'), overwriteEncoding: detectedEncoding => this.encoding.getReadEncoding(resource, options, detectedEncoding) }); // validate binary - if (options && options.acceptTextOnly && decoder.detected.seemsBinary) { + if (options?.acceptTextOnly && decoder.detected.seemsBinary) { throw new TextFileOperationError(localize('fileBinaryError', "File seems to be binary and cannot be opened as text"), TextFileOperationResult.FILE_IS_BINARY, options); } @@ -163,7 +163,7 @@ export class NativeTextFileService extends AbstractTextFileService { // check for overwriteReadonly property (only supported for local file://) try { - if (options && options.overwriteReadonly && resource.scheme === Schemas.file && await exists(resource.fsPath)) { + if (options?.overwriteReadonly && resource.scheme === Schemas.file && await exists(resource.fsPath)) { const fileStat = await stat(resource.fsPath); // try to change mode to writeable @@ -174,7 +174,7 @@ export class NativeTextFileService extends AbstractTextFileService { } // check for writeElevated property (only supported for local file://) - if (options && options.writeElevated && resource.scheme === Schemas.file) { + if (options?.writeElevated && resource.scheme === Schemas.file) { return this.writeElevated(resource, value, options); } @@ -277,7 +277,7 @@ export class NativeTextFileService extends AbstractTextFileService { }; const sudoCommand: string[] = [`"${this.environmentService.cliPath}"`]; - if (options && options.overwriteReadonly) { + if (options?.overwriteReadonly) { sudoCommand.push('--file-chmod'); } @@ -353,7 +353,7 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { // Ensure that we preserve an existing BOM if found for UTF8 // unless we are instructed to overwrite the encoding - const overwriteEncoding = options && options.overwriteEncoding; + const overwriteEncoding = options?.overwriteEncoding; if (!overwriteEncoding && encoding === UTF8) { try { const buffer = (await this.fileService.readFile(resource, { length: UTF8_BOM.length })).value; @@ -381,7 +381,7 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { let preferredEncoding: string | undefined; // Encoding passed in as option - if (options && options.encoding) { + if (options?.encoding) { if (detectedEncoding === UTF8 && options.encoding === UTF8) { preferredEncoding = UTF8_with_bom; // indicate the file has BOM if we are to resolve with UTF 8 } else { diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts index 35c74048a4..bad9a2ebca 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts @@ -15,6 +15,7 @@ import { FileOperationResult, FileOperationError, IFileService } from 'vs/platfo import { IModelService } from 'vs/editor/common/services/modelService'; import { timeout } from 'vs/base/common/async'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; +import { assertIsDefined } from 'vs/base/common/types'; class ServiceAccessor { constructor(@ITextFileService public textFileService: TestTextFileService, @IModelService public modelService: IModelService, @IFileService public fileService: TestFileService) { @@ -286,8 +287,8 @@ suite('Files - TextFileEditorModel', () => { model1.textEditorModel!.setValue('foo'); - const m1Mtime = model1.getStat().mtime; - const m2Mtime = model2.getStat().mtime; + const m1Mtime = assertIsDefined(model1.getStat()).mtime; + const m2Mtime = assertIsDefined(model2.getStat()).mtime; assert.ok(m1Mtime > 0); assert.ok(m2Mtime > 0); @@ -302,8 +303,8 @@ suite('Files - TextFileEditorModel', () => { await accessor.textFileService.saveAll(); assert.ok(!accessor.textFileService.isDirty(toResource.call(this, '/path/index_async.txt'))); assert.ok(!accessor.textFileService.isDirty(toResource.call(this, '/path/index_async2.txt'))); - assert.ok(model1.getStat().mtime > m1Mtime); - assert.ok(model2.getStat().mtime > m2Mtime); + assert.ok(assertIsDefined(model1.getStat()).mtime > m1Mtime); + assert.ok(assertIsDefined(model2.getStat()).mtime > m2Mtime); assert.ok(model1.getLastSaveAttemptTime() > m1Mtime); assert.ok(model2.getLastSaveAttemptTime() > m2Mtime); diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts index 96841bc45c..07461d0992 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts @@ -311,4 +311,4 @@ suite('Files - TextFileEditorModelManager', () => { manager.disposeModel((model as TextFileEditorModel)); manager.dispose(); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/services/themes/browser/fileIconThemeStore.ts b/src/vs/workbench/services/themes/browser/fileIconThemeStore.ts index f1dded774b..2417e5c93e 100644 --- a/src/vs/workbench/services/themes/browser/fileIconThemeStore.ts +++ b/src/vs/workbench/services/themes/browser/fileIconThemeStore.ts @@ -14,6 +14,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { FileIconThemeData } from 'vs/workbench/services/themes/browser/fileIconThemeData'; import { URI } from 'vs/base/common/uri'; import { Disposable } from 'vs/base/common/lifecycle'; +import { find } from 'vs/base/common/arrays'; const iconThemeExtPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'iconThemes', @@ -62,7 +63,7 @@ export class FileIconThemeStore extends Disposable { private initialize() { iconThemeExtPoint.setHandler((extensions) => { - const previousIds: { [key: string]: boolean } = {}; + const previousIds: { [key: string]: boolean; } = {}; const added: FileIconThemeData[] = []; for (const theme of this.knownIconThemes) { previousIds[theme.id] = true; @@ -131,12 +132,7 @@ export class FileIconThemeStore extends Disposable { return Promise.resolve(FileIconThemeData.noIconTheme()); } return this.getFileIconThemes().then(allIconSets => { - for (let iconSet of allIconSets) { - if (iconSet.id === iconTheme) { - return iconSet; - } - } - return undefined; + return find(allIconSets, iconSet => iconSet.id === iconTheme); }); } @@ -145,12 +141,7 @@ export class FileIconThemeStore extends Disposable { return Promise.resolve(FileIconThemeData.noIconTheme()); } return this.getFileIconThemes().then(allIconSets => { - for (let iconSet of allIconSets) { - if (iconSet.settingsId === settingsId) { - return iconSet; - } - } - return undefined; + return find(allIconSets, iconSet => iconSet.settingsId === settingsId); }); } diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 46716acc3f..64579fb075 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -75,15 +75,15 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { private container: HTMLElement; private readonly onColorThemeChange: Emitter; private watchedColorThemeLocation: URI | undefined; - private watchedColorThemeDisposable: IDisposable; + private watchedColorThemeDisposable: IDisposable | undefined; private iconThemeStore: FileIconThemeStore; private currentIconTheme: FileIconThemeData; private readonly onFileIconThemeChange: Emitter; private watchedIconThemeLocation: URI | undefined; - private watchedIconThemeDisposable: IDisposable; + private watchedIconThemeDisposable: IDisposable | undefined; - private themingParticipantChangeListener: IDisposable; + private themingParticipantChangeListener: IDisposable | undefined; private get colorCustomizations(): IColorCustomizations { return this.configurationService.getValue(CUSTOM_WORKBENCH_COLORS_SETTING) || {}; @@ -104,11 +104,12 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { ) { this.container = layoutService.getWorkbenchContainer(); - this.colorThemeStore = new ColorThemeStore(extensionService, ColorThemeData.createLoadedEmptyTheme(DEFAULT_THEME_ID, DEFAULT_THEME_SETTING_VALUE)); + this.colorThemeStore = new ColorThemeStore(extensionService); this.onFileIconThemeChange = new Emitter(); this.iconThemeStore = new FileIconThemeStore(extensionService); this.onColorThemeChange = new Emitter({ leakWarningThreshold: 400 }); + this.currentColorTheme = ColorThemeData.createUnloadedTheme(''); this.currentIconTheme = FileIconThemeData.createUnloadedTheme(''); // In order to avoid paint flashing for tokens, because @@ -179,6 +180,8 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { // restore color this.setColorTheme(prevColorId, 'auto'); prevColorId = undefined; + } else { + this.reloadCurrentColorTheme(); } } } @@ -201,6 +204,8 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { if (this.currentIconTheme.id === DEFAULT_ICON_THEME_ID && !types.isUndefined(prevFileIconId) && await this.iconThemeStore.findThemeData(prevFileIconId)) { this.setFileIconTheme(prevFileIconId, 'auto'); prevFileIconId = undefined; + } else { + this.reloadCurrentFileIconTheme(); } } } @@ -208,18 +213,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.fileService.onFileChanges(async e => { if (this.watchedColorThemeLocation && this.currentColorTheme && e.contains(this.watchedColorThemeLocation, FileChangeType.UPDATED)) { - await this.currentColorTheme.reload(this.fileService); - this.currentColorTheme.setCustomColors(this.colorCustomizations); - this.currentColorTheme.setCustomTokenColors(this.tokenColorCustomizations); - this.updateDynamicCSSRules(this.currentColorTheme); - this.applyTheme(this.currentColorTheme, undefined, false); + this.reloadCurrentColorTheme(); } if (this.watchedIconThemeLocation && this.currentIconTheme && e.contains(this.watchedIconThemeLocation, FileChangeType.UPDATED)) { - await this.currentIconTheme.reload(this.fileService); - _applyIconTheme(this.currentIconTheme, () => { - this.doSetFileIconTheme(this.currentIconTheme); - return Promise.resolve(this.currentIconTheme); - }); + this.reloadCurrentFileIconTheme(); } }); } @@ -364,6 +361,14 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { }); } + private async reloadCurrentColorTheme() { + await this.currentColorTheme.reload(this.fileService); + this.currentColorTheme.setCustomColors(this.colorCustomizations); + this.currentColorTheme.setCustomTokenColors(this.tokenColorCustomizations); + this.updateDynamicCSSRules(this.currentColorTheme); + this.applyTheme(this.currentColorTheme, undefined, false); + } + public restoreColorTheme() { let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); if (colorThemeSetting !== this.currentColorTheme.settingsId) { @@ -389,7 +394,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } private applyTheme(newTheme: ColorThemeData, settingsTarget: ConfigurationTarget | undefined | 'auto', silent = false): Promise { - if (this.currentColorTheme) { + if (this.currentColorTheme.id) { removeClasses(this.container, this.currentColorTheme.id); } else { removeClasses(this.container, VS_DARK_THEME, VS_LIGHT_THEME, VS_HC_THEME); @@ -501,6 +506,14 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { }); } + private async reloadCurrentFileIconTheme() { + await this.currentIconTheme.reload(this.fileService); + _applyIconTheme(this.currentIconTheme, () => { + this.doSetFileIconTheme(this.currentIconTheme); + return Promise.resolve(this.currentIconTheme); + }); + } + public restoreFileIconTheme() { let fileIconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING); if (fileIconThemeSetting !== this.currentIconTheme.settingsId) { diff --git a/src/vs/workbench/services/themes/common/colorThemeStore.ts b/src/vs/workbench/services/themes/common/colorThemeStore.ts index 41049a335d..aabfbeacca 100644 --- a/src/vs/workbench/services/themes/common/colorThemeStore.ts +++ b/src/vs/workbench/services/themes/common/colorThemeStore.ts @@ -57,8 +57,8 @@ export class ColorThemeStore { private readonly onDidChangeEmitter = new Emitter(); public readonly onDidChange: Event = this.onDidChangeEmitter.event; - constructor(@IExtensionService private readonly extensionService: IExtensionService, defaultTheme: ColorThemeData) { - this.extensionsColorThemes = [defaultTheme]; + constructor(@IExtensionService private readonly extensionService: IExtensionService) { + this.extensionsColorThemes = []; this.initialize(); } @@ -69,7 +69,7 @@ export class ColorThemeStore { for (const theme of this.extensionsColorThemes) { previousIds[theme.id] = true; } - this.extensionsColorThemes.length = 1; // remove all but the default theme + this.extensionsColorThemes.length = 0; for (let ext of extensions) { let extensionData = { extensionId: ext.description.identifier.value, @@ -114,11 +114,7 @@ export class ColorThemeStore { } let themeData = ColorThemeData.fromExtensionTheme(theme, colorThemeLocation, extensionData); - if (themeData.id === this.extensionsColorThemes[0].id) { - this.extensionsColorThemes[0] = themeData; - } else { - this.extensionsColorThemes.push(themeData); - } + this.extensionsColorThemes.push(themeData); }); } diff --git a/src/vs/workbench/services/untitled/common/untitledEditorService.ts b/src/vs/workbench/services/untitled/common/untitledEditorService.ts index 6509581150..c3a2213886 100644 --- a/src/vs/workbench/services/untitled/common/untitledEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledEditorService.ts @@ -238,12 +238,12 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor // Look up default language from settings if any if (!mode && !hasAssociatedFilePath) { const configuration = this.configurationService.getValue(); - if (configuration.files && configuration.files.defaultLanguage) { + if (configuration.files?.defaultLanguage) { mode = configuration.files.defaultLanguage; } } - const input = this.instantiationService.createInstance(UntitledEditorInput, untitledResource, hasAssociatedFilePath, mode, initialValue, encoding); + const input = this.instantiationService.createInstance(UntitledEditorInput, untitledResource, !!hasAssociatedFilePath, mode, initialValue, encoding); const contentListener = input.onDidModelChangeContent(() => this._onDidChangeContent.fire(untitledResource)); const dirtyListener = input.onDidChangeDirty(() => this._onDidChangeDirty.fire(untitledResource)); diff --git a/src/vs/workbench/services/url/browser/urlService.ts b/src/vs/workbench/services/url/browser/urlService.ts index a2c8647c95..236237d6ed 100644 --- a/src/vs/workbench/services/url/browser/urlService.ts +++ b/src/vs/workbench/services/url/browser/urlService.ts @@ -54,7 +54,7 @@ export class BrowserURLService extends AbstractURLService { private registerListeners(): void { if (this.provider) { - this._register(this.provider.onCallback(uri => this.open(uri))); + this._register(this.provider.onCallback(uri => this.open(uri, { trusted: true }))); } } diff --git a/src/vs/workbench/services/url/electron-browser/urlService.ts b/src/vs/workbench/services/url/electron-browser/urlService.ts index edb6c6bcdc..8cf6ecbc76 100644 --- a/src/vs/workbench/services/url/electron-browser/urlService.ts +++ b/src/vs/workbench/services/url/electron-browser/urlService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; +import { IURLService, IURLHandler, IOpenURLOptions } from 'vs/platform/url/common/url'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { URLHandlerChannel } from 'vs/platform/url/common/urlIpc'; @@ -15,6 +15,11 @@ import { IElectronEnvironmentService } from 'vs/workbench/services/electron/elec import { createChannelSender } from 'vs/base/parts/ipc/node/ipc'; import { IElectronService } from 'vs/platform/electron/node/electron'; +export interface IRelayOpenURLOptions extends IOpenURLOptions { + openToSide?: boolean; + openExternal?: boolean; +} + export class RelayURLService extends URLService implements IURLHandler { private urlService: IURLService; @@ -46,16 +51,16 @@ export class RelayURLService extends URLService implements IURLHandler { return uri.with({ query }); } - async open(resource: URI, options?: { openToSide?: boolean, openExternal?: boolean }): Promise { + async open(resource: URI, options?: IRelayOpenURLOptions): Promise { if (resource.scheme !== product.urlProtocol) { return false; } - return await this.urlService.open(resource); + return await this.urlService.open(resource, options); } - async handleURL(uri: URI): Promise { - const result = await super.open(uri); + async handleURL(uri: URI, options?: IOpenURLOptions): Promise { + const result = await super.open(uri, options); if (result) { await this.electronService.focusWindow(); diff --git a/src/vs/workbench/services/viewlet/browser/viewlet.ts b/src/vs/workbench/services/viewlet/browser/viewlet.ts index be2d2f2d20..def4e49237 100644 --- a/src/vs/workbench/services/viewlet/browser/viewlet.ts +++ b/src/vs/workbench/services/viewlet/browser/viewlet.ts @@ -23,12 +23,12 @@ export interface IViewletService { /** * Opens a viewlet with the given identifier and pass keyboard focus to it if specified. */ - openViewlet(id: string | undefined, focus?: boolean): Promise; + openViewlet(id: string | undefined, focus?: boolean): Promise; /** - * Returns the current active viewlet or null if none. + * Returns the current active viewlet if any. */ - getActiveViewlet(): IViewlet | null; + getActiveViewlet(): IViewlet | undefined; /** * Returns the id of the default viewlet. @@ -48,7 +48,7 @@ export interface IViewletService { /** * Returns the progress indicator for the side bar. */ - getProgressIndicator(id: string): IProgressIndicator | null; + getProgressIndicator(id: string): IProgressIndicator | undefined; /** * Hide the active viewlet. diff --git a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts index 82436c5c0f..67935a68d6 100644 --- a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts @@ -338,7 +338,7 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi protected getCurrentWorkspaceIdentifier(): IWorkspaceIdentifier | undefined { const workspace = this.contextService.getWorkspace(); - if (workspace && workspace.configuration) { + if (workspace?.configuration) { return { id: workspace.id, configPath: workspace.configuration }; } diff --git a/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts b/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts index 499d205af5..1496e8c92a 100644 --- a/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts @@ -142,6 +142,8 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi return false; } } + + return false; } async isValidTargetWorkspacePath(path: URI): Promise { diff --git a/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts b/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts index 5e965ffb3e..59cc7c9356 100644 --- a/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts @@ -29,7 +29,7 @@ suite('Breadcrumb Model', function () { test('only uri, inside workspace', function () { - let model = new EditorBreadcrumbsModel(URI.parse('foo:/bar/baz/ws/some/path/file.ts'), undefined, workspaceService, configService); + let model = new EditorBreadcrumbsModel(URI.parse('foo:/bar/baz/ws/some/path/file.ts'), undefined, configService, workspaceService); let elements = model.getElements(); assert.equal(elements.length, 3); @@ -44,7 +44,7 @@ suite('Breadcrumb Model', function () { test('only uri, outside workspace', function () { - let model = new EditorBreadcrumbsModel(URI.parse('foo:/outside/file.ts'), undefined, workspaceService, configService); + let model = new EditorBreadcrumbsModel(URI.parse('foo:/outside/file.ts'), undefined, configService, workspaceService); let elements = model.getElements(); assert.equal(elements.length, 2); diff --git a/src/vs/workbench/test/common/editor/editorGroups.test.ts b/src/vs/workbench/test/common/editor/editorGroups.test.ts index 73b806eafe..fcd5010fff 100644 --- a/src/vs/workbench/test/common/editor/editorGroups.test.ts +++ b/src/vs/workbench/test/common/editor/editorGroups.test.ts @@ -112,7 +112,7 @@ class TestFileEditorInput extends EditorInput implements IFileEditorInput { getTypeId() { return 'testFileEditorInputForGroups'; } resolve(): Promise { return Promise.resolve(null!); } setEncoding(encoding: string) { } - getEncoding(): string { return null!; } + getEncoding() { return undefined; } setPreferredEncoding(encoding: string) { } getResource(): URI { return this.resource; } setForceOpenAsBinary(): void { } diff --git a/src/vs/workbench/test/common/notifications.test.ts b/src/vs/workbench/test/common/notifications.test.ts index 46ae2ce2b7..121e03d41f 100644 --- a/src/vs/workbench/test/common/notifications.test.ts +++ b/src/vs/workbench/test/common/notifications.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; 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 { INotification, Severity, NotificationsFilter } from 'vs/platform/notification/common/notification'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; suite('Notifications', () => { @@ -127,6 +127,19 @@ suite('Notifications', () => { assert.equal(links[2].title, 'Click to execute command \'without.title\''); assert.equal(links[2].length, '[Link 3](command:without.title)'.length); assert.equal(links[2].offset, 'Unable to [Link 1](http://link1.com) open [Link 2](command:open.me "Open This") and '.length); + + // Filter + let item8 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message' }, NotificationsFilter.SILENT)!; + assert.equal(item8.silent, true); + + let item9 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message' }, NotificationsFilter.OFF)!; + assert.equal(item9.silent, false); + + let item10 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message' }, NotificationsFilter.ERROR)!; + assert.equal(item10.silent, false); + + let item11 = NotificationViewItem.create({ severity: Severity.Warning, message: 'Error Message' }, NotificationsFilter.ERROR)!; + assert.equal(item11.silent, true); }); test('Model', () => { diff --git a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts index a8f41de893..97d6a6e067 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts @@ -878,12 +878,12 @@ suite('ExtHostLanguageFeatureCommands', function () { let incoming = await commands.executeCommand('vscode.executeCallHierarchyProviderIncomingCalls', model.uri, new types.Position(0, 10)); assert.equal(incoming.length, 1); - assert.ok(incoming[0].source instanceof types.CallHierarchyItem); - assert.equal(incoming[0].source.name, 'IN'); + assert.ok(incoming[0].from instanceof types.CallHierarchyItem); + assert.equal(incoming[0].from.name, 'IN'); let outgoing = await commands.executeCommand('vscode.executeCallHierarchyProviderOutgoingCalls', model.uri, new types.Position(0, 10)); assert.equal(outgoing.length, 1); - assert.ok(outgoing[0].target instanceof types.CallHierarchyItem); - assert.equal(outgoing[0].target.name, 'OUT'); + assert.ok(outgoing[0].to instanceof types.CallHierarchyItem); + assert.equal(outgoing[0].to.name, 'OUT'); }); }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts index f7bcb8b76d..7e47efb085 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts @@ -20,7 +20,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData suite('ExtHostConfiguration', function () { class RecordingShape extends mock() { - lastArgs: [ConfigurationTarget, string, any]; + lastArgs!: [ConfigurationTarget, string, any]; $updateConfigurationOption(target: ConfigurationTarget, key: string, value: any): Promise { this.lastArgs = [target, key, value]; return Promise.resolve(undefined); 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 8027d1bc60..25b4751af4 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts @@ -6,7 +6,7 @@ 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, IStatusMessageOptions } from 'vs/platform/notification/common/notification'; +import { INotificationService, INotification, NoOpNotification, INotificationHandle, Severity, IPromptChoice, IPromptOptions, IStatusMessageOptions, NotificationsFilter } 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 { IDisposable, Disposable } from 'vs/base/common/lifecycle'; @@ -55,6 +55,9 @@ const emptyNotificationService = new class implements INotificationService { status(message: string | Error, options?: IStatusMessageOptions): IDisposable { return Disposable.None; } + setFilter(filter: NotificationsFilter): void { + throw new Error('not implemented.'); + } }; class EmptyNotificationService implements INotificationService { @@ -78,11 +81,14 @@ class EmptyNotificationService implements INotificationService { throw new Error('Method not implemented.'); } prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions): INotificationHandle { - throw new Error('not implemented'); + throw new Error('Method not implemented'); } status(message: string, options?: IStatusMessageOptions): IDisposable { return Disposable.None; } + setFilter(filter: NotificationsFilter): void { + throw new Error('Method not implemented.'); + } } 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 3b8e5095c6..30dfc07729 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts @@ -28,7 +28,7 @@ const disposables = new DisposableStore(); let mockMainThreadSearch: MockMainThreadSearch; class MockMainThreadSearch implements MainThreadSearchShape { - lastHandle: number; + lastHandle!: number; results: Array = []; diff --git a/src/vs/workbench/test/electron-browser/api/extHostTypeConverter.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTypeConverter.test.ts index f9e7502b50..4a8822c3b0 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTypeConverter.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTypeConverter.test.ts @@ -7,9 +7,10 @@ import * as assert from 'assert'; import { MarkdownString, LogLevel } from 'vs/workbench/api/common/extHostTypeConverters'; import { isEmptyObject } from 'vs/base/common/types'; -import { size } from 'vs/base/common/collections'; +import { size, forEach } from 'vs/base/common/collections'; import * as types from 'vs/workbench/api/common/extHostTypes'; import { LogLevel as _MainLogLevel } from 'vs/platform/log/common/log'; +import { URI } from 'vs/base/common/uri'; suite('ExtHostTypeConverter', function () { @@ -21,11 +22,13 @@ suite('ExtHostTypeConverter', function () { data = MarkdownString.from('Hello [link](foo)'); assert.equal(data.value, 'Hello [link](foo)'); - assert.equal(isEmptyObject(data.uris), true); // no scheme, no uri + assert.equal(size(data.uris!), 1); + assert.ok(!!data.uris!['foo']); data = MarkdownString.from('Hello [link](www.noscheme.bad)'); assert.equal(data.value, 'Hello [link](www.noscheme.bad)'); - assert.equal(isEmptyObject(data.uris), true); // no scheme, no uri + assert.equal(size(data.uris!), 1); + assert.ok(!!data.uris!['www.noscheme.bad']); data = MarkdownString.from('Hello [link](foo:path)'); assert.equal(data.value, 'Hello [link](foo:path)'); @@ -59,6 +62,20 @@ suite('ExtHostTypeConverter', function () { assert.ok(!!data.uris!['file:///somepath/here2']); }); + test('NPM script explorer running a script from the hover does not work #65561', function () { + + let data = MarkdownString.from('*hello* [click](command:npm.runScriptFromHover?%7B%22documentUri%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22file%3A%2F%2F%2Fc%253A%2Ffoo%2Fbaz.ex%22%2C%22path%22%3A%22%2Fc%3A%2Ffoo%2Fbaz.ex%22%2C%22scheme%22%3A%22file%22%7D%2C%22script%22%3A%22dev%22%7D)'); + // assert that both uri get extracted but that the latter is only decoded once... + assert.equal(size(data.uris!), 2); + forEach(data.uris!, entry => { + if (entry.value.scheme === 'file') { + assert.ok(URI.revive(entry.value).toString().indexOf('file:///c%3A') === 0); + } else { + assert.equal(entry.value.scheme, 'command'); + } + }); + }); + test('LogLevel', () => { assert.equal(LogLevel.from(types.LogLevel.Error), _MainLogLevel.Error); assert.equal(LogLevel.from(types.LogLevel.Info), _MainLogLevel.Info); 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 5bfdfe2bee..3c18e894d1 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts @@ -11,6 +11,7 @@ import * as vscode from 'vscode'; import { SingleProxyRPCProtocol } from './testRPCProtocol'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { URI } from 'vs/base/common/uri'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; suite('ExtHostWebview', () => { @@ -22,7 +23,7 @@ suite('ExtHostWebview', () => { webviewCspSource: '', webviewResourceRoot: '', isExtensionDevelopmentDebug: false, - }); + }, undefined); let lastInvokedDeserializer: vscode.WebviewPanelSerializer | undefined = undefined; @@ -32,21 +33,23 @@ suite('ExtHostWebview', () => { } } + const extension = {} as IExtensionDescription; + const serializerA = new NoopSerializer(); const serializerB = new NoopSerializer(); - const serializerARegistration = extHostWebviews.registerWebviewPanelSerializer(viewType, serializerA); + const serializerARegistration = extHostWebviews.registerWebviewPanelSerializer(extension, viewType, serializerA); await extHostWebviews.$deserializeWebviewPanel('x', viewType, 'title', {}, 0 as EditorViewColumn, {}); assert.strictEqual(lastInvokedDeserializer, serializerA); assert.throws( - () => extHostWebviews.registerWebviewPanelSerializer(viewType, serializerB), + () => extHostWebviews.registerWebviewPanelSerializer(extension, viewType, serializerB), 'Should throw when registering two serializers for the same view'); serializerARegistration.dispose(); - extHostWebviews.registerWebviewPanelSerializer(viewType, serializerB); + extHostWebviews.registerWebviewPanelSerializer(extension, viewType, serializerB); await extHostWebviews.$deserializeWebviewPanel('x', viewType, 'title', {}, 0 as EditorViewColumn, {}); assert.strictEqual(lastInvokedDeserializer, serializerB); @@ -58,7 +61,7 @@ suite('ExtHostWebview', () => { webviewCspSource: '', webviewResourceRoot: 'vscode-resource://{{resource}}', isExtensionDevelopmentDebug: false, - }); + }, undefined); const webview = extHostWebviews.createWebviewPanel({} as any, 'type', 'title', 1, {}); assert.strictEqual( @@ -99,7 +102,7 @@ suite('ExtHostWebview', () => { webviewCspSource: '', webviewResourceRoot: `https://{{uuid}}.webview.contoso.com/commit/{{resource}}`, isExtensionDevelopmentDebug: false, - }); + }, undefined); const webview = extHostWebviews.createWebviewPanel({} as any, 'type', 'title', 1, {}); function stripEndpointUuid(input: string) { diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts index 7f9913044b..02e8c84347 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts @@ -79,7 +79,7 @@ suite('MainThreadDocumentsAndEditors', () => { onDidPanelOpen = Event.None; onDidPanelClose = Event.None; getActivePanel() { - return null; + return undefined; } }, TestEnvironmentService 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 c7ab29204b..264a693dd3 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts @@ -111,7 +111,7 @@ suite('MainThreadEditors', () => { onDidPanelOpen = Event.None; onDidPanelClose = Event.None; getActivePanel() { - return null; + return undefined; } }, TestEnvironmentService diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 56f04116a7..d537cb5a9a 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -87,10 +87,11 @@ import { IProductService } from 'vs/platform/product/common/productService'; import product from 'vs/platform/product/common/product'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IElectronService } from 'vs/platform/electron/node/electron'; -import { INativeOpenDialogOptions, MessageBoxReturnValue, SaveDialogReturnValue, OpenDialogReturnValue } from 'vs/platform/dialogs/node/dialogs'; +import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { IBackupMainService, IWorkspaceBackupInfo } from 'vs/platform/backup/electron-main/backup'; import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; +import { find } from 'vs/base/common/arrays'; export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); @@ -186,11 +187,11 @@ export class TestContextService implements IWorkspaceContextService { } export class TestTextFileService extends NativeTextFileService { - public cleanupBackupsBeforeShutdownCalled: boolean; + public cleanupBackupsBeforeShutdownCalled!: boolean; - private promptPath: URI; - private confirmResult: ConfirmResult; - private resolveTextContentError: FileOperationError | null; + private promptPath!: URI; + private confirmResult!: ConfirmResult; + private resolveTextContentError!: FileOperationError | null; constructor( @IWorkspaceContextService contextService: IWorkspaceContextService, @@ -461,15 +462,11 @@ export class TestLayoutService implements IWorkbenchLayoutService { onCenteredLayoutChange: Event = Event.None; onFullscreenChange: Event = Event.None; onPanelPositionChange: Event = Event.None; + onPartVisibilityChange: Event = Event.None; onLayout = Event.None; - private readonly _onTitleBarVisibilityChange = new Emitter(); private readonly _onMenubarVisibilityChange = new Emitter(); - public get onTitleBarVisibilityChange(): Event { - return this._onTitleBarVisibilityChange.event; - } - public get onMenubarVisibilityChange(): Event { return this._onMenubarVisibilityChange.event; } @@ -582,8 +579,8 @@ export class TestViewletService implements IViewletService { onDidViewletOpen = this.onDidViewletOpenEmitter.event; onDidViewletClose = this.onDidViewletCloseEmitter.event; - public openViewlet(id: string, focus?: boolean): Promise { - return Promise.resolve(null!); + public openViewlet(id: string, focus?: boolean): Promise { + return Promise.resolve(undefined); } public getViewlets(): ViewletDescriptor[] { @@ -610,7 +607,7 @@ export class TestViewletService implements IViewletService { } public getProgressIndicator(id: string) { - return null!; + return undefined; } public hideActiveViewlet(): void { } @@ -626,19 +623,19 @@ export class TestPanelService implements IPanelService { onDidPanelOpen = new Emitter<{ panel: IPanel, focus: boolean }>().event; onDidPanelClose = new Emitter().event; - public openPanel(id: string, focus?: boolean): IPanel { - return null!; + public openPanel(id: string, focus?: boolean): undefined { + return undefined; } public getPanel(id: string): any { return activeViewlet; } - public getPanels(): any[] { + public getPanels() { return []; } - public getPinnedPanels(): any[] { + public getPinnedPanels() { return []; } @@ -700,14 +697,8 @@ export class TestEditorGroupsService implements IEditorGroupsService { return this.groups; } - getGroup(identifier: number): IEditorGroup { - for (const group of this.groups) { - if (group.id === identifier) { - return group; - } - } - - return undefined!; + getGroup(identifier: number): IEditorGroup | undefined { + return find(this.groups, group => group.id === identifier); } getLabel(_identifier: number): string { @@ -762,7 +753,7 @@ export class TestEditorGroupsService implements IEditorGroupsService { return false; } - partOptions: IEditorPartOptions; + partOptions!: IEditorPartOptions; enforcePartOptions(options: IEditorPartOptions): IDisposable { return Disposable.None; } @@ -773,20 +764,20 @@ export class TestEditorGroup implements IEditorGroupView { constructor(public id: number) { } get group(): EditorGroup { throw new Error('not implemented'); } - activeControl: IVisibleEditor; - activeEditor: IEditorInput; - previewEditor: IEditorInput; - count: number; - disposed: boolean; + activeControl!: IVisibleEditor; + activeEditor!: IEditorInput; + previewEditor!: IEditorInput; + count!: number; + disposed!: boolean; editors: ReadonlyArray = []; - label: string; - index: number; + label!: string; + index!: number; whenRestored: Promise = Promise.resolve(undefined); - element: HTMLElement; - minimumWidth: number; - maximumWidth: number; - minimumHeight: number; - maximumHeight: number; + element!: HTMLElement; + minimumWidth!: number; + maximumWidth!: number; + minimumHeight!: number; + maximumHeight!: number; isEmpty = true; isMinimized = false; @@ -877,9 +868,9 @@ export class TestEditorService implements EditorServiceImpl { onDidCloseEditor: Event = Event.None; onDidOpenEditorFail: Event = Event.None; - activeControl: IVisibleEditor; + activeControl!: IVisibleEditor; activeTextEditorWidget: any; - activeEditor: IEditorInput; + activeEditor!: IEditorInput; editors: ReadonlyArray = []; visibleControls: ReadonlyArray = []; visibleTextEditorWidgets = []; @@ -929,7 +920,7 @@ export class TestFileService implements IFileService { readonly onError: Event = Event.None; private content = 'Hello Html'; - private lastReadFileUri: URI; + private lastReadFileUri!: URI; constructor() { this._onFileChanges = new Emitter(); @@ -1170,22 +1161,22 @@ export class TestCodeEditorService implements ICodeEditorService { addDiffEditor(_editor: IDiffEditor): void { } removeDiffEditor(_editor: IDiffEditor): void { } listDiffEditors(): IDiffEditor[] { return []; } - getFocusedCodeEditor(): ICodeEditor | undefined { return undefined; } + getFocusedCodeEditor(): ICodeEditor | null { return null; } registerDecorationType(_key: string, _options: IDecorationRenderOptions, _parentTypeKey?: string): void { } removeDecorationType(_key: string): void { } resolveDecorationOptions(_typeKey: string, _writable: boolean): IModelDecorationOptions { return Object.create(null); } setTransientModelProperty(_model: ITextModel, _key: string, _value: any): void { } getTransientModelProperty(_model: ITextModel, _key: string) { } - getActiveCodeEditor(): ICodeEditor | undefined { return undefined; } - openCodeEditor(_input: IResourceInput, _source: ICodeEditor, _sideBySide?: boolean): Promise { return Promise.resolve(undefined); } + getActiveCodeEditor(): ICodeEditor | null { return null; } + openCodeEditor(_input: IResourceInput, _source: ICodeEditor, _sideBySide?: boolean): Promise { return Promise.resolve(null); } } export class TestLifecycleService implements ILifecycleService { public _serviceBrand: undefined; - public phase: LifecyclePhase; - public startupKind: StartupKind; + public phase!: LifecyclePhase; + public startupKind!: StartupKind; private readonly _onBeforeShutdown = new Emitter(); private readonly _onWillShutdown = new Emitter(); @@ -1246,12 +1237,10 @@ export class TestTextResourcePropertiesService implements ITextResourcePropertie ) { } - getEOL(resource: URI): string { - const filesConfiguration = this.configurationService.getValue<{ eol: string }>('files'); - if (filesConfiguration && filesConfiguration.eol) { - if (filesConfiguration.eol !== 'auto') { - return filesConfiguration.eol; - } + getEOL(resource: URI, language?: string): string { + const eol = this.configurationService.getValue('files.eol', { overrideIdentifier: language, resource }); + if (eol && eol !== 'auto') { + return eol; } return (isLinux || isMacintosh) ? '\n' : '\r\n'; } @@ -1317,7 +1306,6 @@ export class TestHostService implements IHostService { async restart(): Promise { } async reload(): Promise { } - async closeWorkspace(): Promise { } async focus(): Promise { } @@ -1355,9 +1343,9 @@ export class TestElectronService implements IElectronService { async minimizeWindow(): Promise { } async isWindowFocused(): Promise { return true; } async focusWindow(options?: { windowId?: number | undefined; } | undefined): Promise { } - async showMessageBox(options: Electron.MessageBoxOptions): Promise { throw new Error('Method not implemented.'); } - async showSaveDialog(options: Electron.SaveDialogOptions): Promise { throw new Error('Method not implemented.'); } - async showOpenDialog(options: Electron.OpenDialogOptions): Promise { throw new Error('Method not implemented.'); } + async showMessageBox(options: Electron.MessageBoxOptions): Promise { throw new Error('Method not implemented.'); } + async showSaveDialog(options: Electron.SaveDialogOptions): Promise { throw new Error('Method not implemented.'); } + async showOpenDialog(options: Electron.OpenDialogOptions): Promise { throw new Error('Method not implemented.'); } async pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise { } async pickFileAndOpen(options: INativeOpenDialogOptions): Promise { } async pickFolderAndOpen(options: INativeOpenDialogOptions): Promise { } @@ -1375,14 +1363,13 @@ export class TestElectronService implements IElectronService { async toggleWindowTabsBar(): Promise { } async relaunch(options?: { addArgs?: string[] | undefined; removeArgs?: string[] | undefined; } | undefined): Promise { } async reload(): Promise { } - async closeWorkspace(): Promise { } async closeWindow(): Promise { } async quit(): Promise { } async openDevTools(options?: Electron.OpenDevToolsOptions | undefined): Promise { } async toggleDevTools(): Promise { } async startCrashReporter(options: Electron.CrashReporterStartOptions): Promise { } async resolveProxy(url: string): Promise { return undefined; } - async openExtensionDevelopmentHostWindow(args: minimist.ParsedArgs, env: IProcessEnvironment): Promise { } + async openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment): Promise { } } export class TestBackupMainService implements IBackupMainService { @@ -1448,15 +1435,15 @@ export class TestDialogMainService implements IDialogMainService { throw new Error('Method not implemented.'); } - showMessageBox(options: Electron.MessageBoxOptions, window?: Electron.BrowserWindow | undefined): Promise { + showMessageBox(options: Electron.MessageBoxOptions, window?: Electron.BrowserWindow | undefined): Promise { throw new Error('Method not implemented.'); } - showSaveDialog(options: Electron.SaveDialogOptions, window?: Electron.BrowserWindow | undefined): Promise { + showSaveDialog(options: Electron.SaveDialogOptions, window?: Electron.BrowserWindow | undefined): Promise { throw new Error('Method not implemented.'); } - showOpenDialog(options: Electron.OpenDialogOptions, window?: Electron.BrowserWindow | undefined): Promise { + showOpenDialog(options: Electron.OpenDialogOptions, window?: Electron.BrowserWindow | undefined): Promise { throw new Error('Method not implemented.'); } } diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 733d6c370b..c6a25c35d8 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -81,6 +81,7 @@ import 'vs/workbench/services/extensionManagement/common/extensionEnablementServ import 'vs/workbench/services/notification/common/notificationService'; import 'vs/workbench/services/extensions/common/staticExtensions'; import 'vs/workbench/services/userDataSync/common/settingsMergeService'; +import 'vs/workbench/services/path/common/remotePathService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 0f6f9220a6..aaee87fbcc 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -63,6 +63,7 @@ import 'vs/workbench/services/update/electron-browser/updateService'; import 'vs/workbench/services/issue/electron-browser/issueService'; import 'vs/workbench/services/menubar/electron-browser/menubarService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; @@ -124,9 +125,6 @@ import 'vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution // Execution import 'vs/workbench/contrib/externalTerminal/node/externalTerminalService'; -// Update -import 'vs/workbench/contrib/update/electron-browser/update.contribution'; - // Performance import 'vs/workbench/contrib/performance/electron-browser/performance.contribution'; @@ -145,12 +143,11 @@ import 'vs/workbench/contrib/tasks/electron-browser/taskService'; // User Data Sync import 'vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution'; -// Welcome -import 'vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.contribution'; +// Telemetry Opt Out +import 'vs/workbench/contrib/welcome/telemetryOptOut/electron-browser/telemetryOptOut.contribution'; // Configuration Exporter import 'vs/workbench/contrib/configExporter/node/configurationExportHelper.contribution'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; //#endregion diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index cb240dfbb4..f643c6e7e2 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -150,5 +150,5 @@ export { // Updates IUpdateProvider, - IUpdate + IUpdate, }; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index d3f2c6d04e..1aac178d8a 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -96,7 +96,7 @@ import 'vs/workbench/contrib/debug/browser/extensionHostDebugService'; // Webview import 'vs/workbench/contrib/webview/browser/webviewService'; -import 'vs/workbench/contrib/webview/browser/webviewEditorService'; +import 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; // Terminal import 'vs/workbench/contrib/terminal/browser/terminalNativeService'; @@ -105,7 +105,7 @@ import 'vs/workbench/contrib/terminal/browser/terminalInstanceService'; // Tasks import 'vs/workbench/contrib/tasks/browser/taskService'; -// Welcome -import 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution'; +// Telemetry Opt Out +import 'vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.contribution'; //#endregion diff --git a/test/automation/src/keybindings.ts b/test/automation/src/keybindings.ts index 55ac104f08..2100ab974a 100644 --- a/test/automation/src/keybindings.ts +++ b/test/automation/src/keybindings.ts @@ -24,7 +24,7 @@ export class KeybindingsEditor { await this.code.waitAndClick('.keybindings-list-container .monaco-list-row.keybinding-item'); await this.code.waitForElement('.keybindings-list-container .monaco-list-row.keybinding-item.focused.selected'); - await this.code.waitAndClick('.keybindings-list-container .monaco-list-row.keybinding-item .action-item .codicon.add'); + await this.code.waitAndClick('.keybindings-list-container .monaco-list-row.keybinding-item .action-item .codicon.codicon-add'); await this.code.waitForActiveElement('.defineKeybindingWidget .monaco-inputbox input'); await this.code.dispatchKeybinding(keybinding); diff --git a/test/automation/src/problems.ts b/test/automation/src/problems.ts index 4701750088..4522b08470 100644 --- a/test/automation/src/problems.ts +++ b/test/automation/src/problems.ts @@ -39,7 +39,7 @@ export class Problems { } public static getSelectorInProblemsView(problemType: ProblemSeverity): string { - let selector = problemType === ProblemSeverity.WARNING ? 'severity-warning' : 'severity-error'; + let selector = problemType === ProblemSeverity.WARNING ? 'codicon-warning' : 'codicon-error'; return `div[id="workbench.panel.markers"] .monaco-tl-contents .marker-icon.${selector}`; } diff --git a/test/automation/src/puppeteerDriver.ts b/test/automation/src/puppeteerDriver.ts index fd3291fe74..64ae0b7c8c 100644 --- a/test/automation/src/puppeteerDriver.ts +++ b/test/automation/src/puppeteerDriver.ts @@ -92,9 +92,17 @@ let endpoint: string | undefined; export async function launch(_args: string[]): Promise { args = _args; - const webUserDataDir = args.filter(e => e.includes('--user-data-dir='))[0].replace('--user-data-dir=', ''); - await promisify(mkdir)(webUserDataDir); - server = spawn(join(args[0], `resources/server/web.${process.platform === 'win32' ? 'bat' : 'sh'}`), ['--browser', 'none', '--driver', 'web', '--web-user-data-dir', webUserDataDir]); + const agentFolder = args.filter(e => e.includes('--user-data-dir='))[0].replace('--user-data-dir=', ''); + await promisify(mkdir)(agentFolder); + const env = { + VSCODE_AGENT_FOLDER: agentFolder, + ...process.env + }; + server = spawn( + join(args[0], `resources/server/web.${process.platform === 'win32' ? 'bat' : 'sh'}`), + ['--browser', 'none', '--driver', 'web'], + { env } + ); server.stderr.on('data', e => console.log('Server stderr: ' + e)); server.stdout.on('data', e => console.log('Server stdout: ' + e)); process.on('exit', teardown); diff --git a/test/automation/src/scm.ts b/test/automation/src/scm.ts index 7fed8d48b0..78c4dda9fd 100644 --- a/test/automation/src/scm.ts +++ b/test/automation/src/scm.ts @@ -9,7 +9,7 @@ import { findElement, findElements, Code } from './code'; const VIEWLET = 'div[id="workbench.view.scm"]'; const SCM_INPUT = `${VIEWLET} .scm-editor textarea`; -const SCM_RESOURCE = `${VIEWLET} .monaco-list-row > .resource`; +const SCM_RESOURCE = `${VIEWLET} .monaco-list-row .resource`; const REFRESH_COMMAND = `div[id="workbench.parts.sidebar"] .actions-container a.action-label[title="Refresh"]`; const COMMIT_COMMAND = `div[id="workbench.parts.sidebar"] .actions-container a.action-label[title="Commit"]`; const SCM_RESOURCE_CLICK = (name: string) => `${SCM_RESOURCE} .monaco-icon-label[title*="${name}"] .label-name`; @@ -67,7 +67,7 @@ export class SCM extends Viewlet { async unstage(name: string): Promise { await this.code.waitAndClick(SCM_RESOURCE_ACTION_CLICK(name, 'Unstage Changes')); - await this.waitForChange('app.js', 'Modified'); + await this.waitForChange(name, 'Modified'); } async commit(message: string): Promise { diff --git a/test/automation/src/search.ts b/test/automation/src/search.ts index fc46e510ec..92902f604d 100644 --- a/test/automation/src/search.ts +++ b/test/automation/src/search.ts @@ -84,11 +84,11 @@ export class Search extends Viewlet { } async expandReplace(): Promise { - await this.code.waitAndClick(`${VIEWLET} .search-widget .monaco-button.toggle-replace-button.collapse`); + await this.code.waitAndClick(`${VIEWLET} .search-widget .monaco-button.toggle-replace-button.codicon-chevron-right`); } async collapseReplace(): Promise { - await this.code.waitAndClick(`${VIEWLET} .search-widget .monaco-button.toggle-replace-button.expand`); + await this.code.waitAndClick(`${VIEWLET} .search-widget .monaco-button.toggle-replace-button.codicon-chevron-down`); } async setReplaceText(text: string): Promise { @@ -100,12 +100,12 @@ export class Search extends Viewlet { await retry( () => this.code.waitAndClick(fileMatch), - () => this.code.waitForElement(`${fileMatch} .action-label.codicon.action-replace-all`, el => !!el && el.top > 0 && el.left > 0, 10) + () => this.code.waitForElement(`${fileMatch} .action-label.codicon.codicon-replace-all`, el => !!el && el.top > 0 && el.left > 0, 10) ); // ¯\_(ツ)_/¯ await new Promise(c => setTimeout(c, 500)); - await this.code.waitAndClick(`${fileMatch} .action-label.codicon.action-replace-all`); + await this.code.waitAndClick(`${fileMatch} .action-label.codicon.codicon-replace-all`); } async waitForResultText(text: string): Promise { diff --git a/test/automation/src/statusbar.ts b/test/automation/src/statusbar.ts index c2c7dfaac0..4e015d0f10 100644 --- a/test/automation/src/statusbar.ts +++ b/test/automation/src/statusbar.ts @@ -44,11 +44,11 @@ export class StatusBar { private getSelector(element: StatusBarElement): string { switch (element) { case StatusBarElement.BRANCH_STATUS: - return `${this.mainSelector} ${this.leftSelector} .octicon.octicon-git-branch`; + return `${this.mainSelector} ${this.leftSelector} .codicon.codicon-git-branch`; case StatusBarElement.SYNC_STATUS: - return `${this.mainSelector} ${this.leftSelector} .octicon.octicon-sync`; + return `${this.mainSelector} ${this.leftSelector} .codicon.codicon-sync`; case StatusBarElement.PROBLEMS_STATUS: - return `${this.mainSelector} ${this.leftSelector} .octicon.octicon-error`; + return `${this.mainSelector} ${this.leftSelector} .codicon.codicon-error`; case StatusBarElement.SELECTION_STATUS: return `${this.mainSelector} ${this.rightSelector}[title="Go to Line"]`; case StatusBarElement.INDENTATION_STATUS: diff --git a/test/electron/index.js b/test/electron/index.js index c5dc1a393f..94c2cb0018 100644 --- a/test/electron/index.js +++ b/test/electron/index.js @@ -9,7 +9,7 @@ const { join } = require('path'); const path = require('path'); const mocha = require('mocha'); const events = require('events'); -const MochaJUnitReporter = require('mocha-junit-reporter'); +// const MochaJUnitReporter = require('mocha-junit-reporter'); const url = require('url'); const defaultReporterName = process.platform === 'win32' ? 'list' : 'spec'; @@ -134,12 +134,13 @@ app.on('ready', () => { if (argv.tfs) { new mocha.reporters.Spec(runner); - new MochaJUnitReporter(runner, { - reporterOptions: { - testsuitesTitle: `${argv.tfs} ${process.platform}`, - mochaFile: process.env.BUILD_ARTIFACTSTAGINGDIRECTORY ? path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${argv.tfs.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`) : undefined - } - }); + // TODO@deepak the mocha Junit reporter seems to cause a hang when running with Electron 6 inside docker container + // new MochaJUnitReporter(runner, { + // reporterOptions: { + // testsuitesTitle: `${argv.tfs} ${process.platform}`, + // mochaFile: process.env.BUILD_ARTIFACTSTAGINGDIRECTORY ? path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${argv.tfs.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`) : undefined + // } + // }); } else { const reporterPath = path.join(path.dirname(require.resolve('mocha')), 'lib', 'reporters', argv.reporter); let Reporter; diff --git a/test/smoke/README.md b/test/smoke/README.md index dc90521195..5f86605f15 100644 --- a/test/smoke/README.md +++ b/test/smoke/README.md @@ -43,7 +43,12 @@ yarn smoketest --build PATH_TO_NEW_RELEASE_PARENT_FOLDER --stable-build PATH_TO_ ### Develop -Start a watch task in `test/smoke`: +Start two watch tasks: + +```bash +cd test/automation +yarn watch +``` ```bash cd test/smoke diff --git a/test/smoke/src/areas/git/git.test.ts b/test/smoke/src/areas/git/git.test.ts index 61d251eebd..9902a45ed3 100644 --- a/test/smoke/src/areas/git/git.test.ts +++ b/test/smoke/src/areas/git/git.test.ts @@ -51,6 +51,7 @@ export function setup() { await app.workbench.scm.waitForChange('app.js', 'Modified'); await app.workbench.scm.stage('app.js'); + await app.workbench.scm.openChange('app.js'); await app.workbench.scm.unstage('app.js'); }); @@ -60,6 +61,7 @@ export function setup() { await app.workbench.scm.openSCMViewlet(); await app.workbench.scm.waitForChange('app.js', 'Modified'); + await app.workbench.scm.openChange('app.js'); await app.workbench.scm.stage('app.js'); await app.workbench.scm.commit('first commit'); diff --git a/yarn.lock b/yarn.lock index 71732b3546..fcb1d5bbb4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -135,69 +135,6 @@ lodash "^4.17.11" to-fast-properties "^2.0.0" -"@microsoft/applicationinsights-analytics-js@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-2.1.1.tgz#6d09c1915f808026e2d45165d04802f09affed59" - integrity sha512-VKIutoFKY99CyKwxLUuj6Vnq14/QwXo9/QSQDpYnHEjo+uKn7QmLsHqWw0K9uYNfNAXt4BZimX/zDg6jZtzeXg== - dependencies: - "@microsoft/applicationinsights-common" "2.1.1" - "@microsoft/applicationinsights-core-js" "2.1.1" - tslib "^1.9.3" - -"@microsoft/applicationinsights-channel-js@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.1.1.tgz#e205eddd93e49d17d9e0711a612b4bfc9810888f" - integrity sha512-fYr9IAqtaEr9AmaPaL3SLQVT3t3GQzl+n74gpNKyAVakDIm0nYQ/bimjdcAhJMDf1VGNSPg/xICneyuZg7Wxlg== - dependencies: - "@microsoft/applicationinsights-common" "2.1.1" - "@microsoft/applicationinsights-core-js" "2.1.1" - tslib "^1.9.3" - -"@microsoft/applicationinsights-common@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-common/-/applicationinsights-common-2.1.1.tgz#27e6074584a7a3a8ca3f11f7ff2b7ff0f395bf2d" - integrity sha512-2hkS1Ia1FmAjCuYZ5JlG20/WgObqdsKtmK5YALAFGHIB4KSQ/Za1qazS+7GsG+E0F9UJivNWL1geUIcNqg5Qjg== - dependencies: - "@microsoft/applicationinsights-core-js" "2.1.1" - tslib "^1.9.3" - -"@microsoft/applicationinsights-core-js@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.1.1.tgz#30fb6a519cc1c6119c419c4811ce72c260217d9e" - integrity sha512-4t4wf6SKqIcWEQDPg/uOhm+BxtHhu/AFreyEoYZmMfcxzAu33h1FtTQRtxBNbYH1+thiNZCh80yUpnT7d9Hrlw== - dependencies: - tslib "^1.9.3" - -"@microsoft/applicationinsights-dependencies-js@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-2.1.1.tgz#8154c3efcb24617d015d0bce7c2cc47797a8d3c4" - integrity sha512-yhb4EToBp+aI+qLo0h5NDNtoo3sDFV60uyIOK843YjzXqVotcXX/lRShlghTkJtYH09QhrdzDjViUHnD4sMFSQ== - dependencies: - "@microsoft/applicationinsights-common" "2.1.1" - "@microsoft/applicationinsights-core-js" "2.1.1" - tslib "^1.9.3" - -"@microsoft/applicationinsights-properties-js@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-2.1.1.tgz#ca34232766eb16167b5d87693e2ae5d94f2a1559" - integrity sha512-8l+/ppw6xKTam2RL4EHZ52Lcf217olw81j6kyBNKtIcGwSnLNHrFwEeF3vBWIteG2JKzlg1GhGjrkB3oxXsV2g== - dependencies: - "@microsoft/applicationinsights-common" "2.1.1" - "@microsoft/applicationinsights-core-js" "2.1.1" - tslib "^1.9.3" - -"@microsoft/applicationinsights-web@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web/-/applicationinsights-web-2.1.1.tgz#1a44eddda7c244b88d9eb052dab6c855682e4f05" - integrity sha512-crvhCkNsNxkFuPWmttyWNSAA96D5FxBtKS6UA9MV9f9XHevTfchf/E3AuU9JZcsXufWMQLwLrUQ9ZiA1QJ0EWA== - dependencies: - "@microsoft/applicationinsights-analytics-js" "2.1.1" - "@microsoft/applicationinsights-channel-js" "2.1.1" - "@microsoft/applicationinsights-common" "2.1.1" - "@microsoft/applicationinsights-core-js" "2.1.1" - "@microsoft/applicationinsights-dependencies-js" "2.1.1" - "@microsoft/applicationinsights-properties-js" "2.1.1" - "@types/chart.js@^2.7.31": version "2.7.48" resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.7.48.tgz#db7b6d6ed33659f97ee49181f22c980bb0790a7b" @@ -667,10 +604,10 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -anymatch@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.0.tgz#e609350e50a9313b472789b2f14ef35808ee14d6" - integrity sha512-Ozz7l4ixzI7Oxj2+cw+p0tVUt27BpaJ+1+q1TCeANWxHpvyn2+Un+YamBdfKu0uh8xLodGhoa1v7595NhKDAuA== +anymatch@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== dependencies: normalize-path "^3.0.0" picomatch "^2.0.4" @@ -1199,7 +1136,7 @@ braces@^2.3.0, braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" -braces@^3.0.2: +braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -1528,20 +1465,20 @@ cheerio@^1.0.0-rc.1: lodash "^4.15.0" parse5 "^3.0.1" -chokidar@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.1.0.tgz#ff23d077682a90eadd209bfa76eb10ed6d359668" - integrity sha512-6vZfo+7W0EOlbSo0nhVKMz4yyssrwiPbBZ8wj1lq8/+l4ZhGZ2U4Md7PspvmijXp1a26D3B7AHEBmIB7aVtaOQ== +chokidar@3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.2.2.tgz#a433973350021e09f2b853a2287781022c0dc935" + integrity sha512-bw3pm7kZ2Wa6+jQWYP/c7bAZy3i4GwiIiMO2EeRjrE48l8vBqC/WvFhSF0xyM8fQiPEGvwMY/5bqDG7sSEOuhg== dependencies: - anymatch "^3.1.0" - braces "^3.0.2" - glob-parent "^5.0.0" - is-binary-path "^2.1.0" - is-glob "^4.0.1" - normalize-path "^3.0.0" - readdirp "^3.1.1" + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.2.0" optionalDependencies: - fsevents "^2.0.6" + fsevents "~2.1.1" chokidar@^2.0.0: version "2.1.0" @@ -3395,10 +3332,10 @@ fsevents@^1.2.7: nan "^2.9.2" node-pre-gyp "^0.10.0" -fsevents@^2.0.6: - version "2.0.7" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.0.7.tgz#382c9b443c6cbac4c57187cdda23aa3bf1ccfc2a" - integrity sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ== +fsevents@~2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.1.tgz#74c64e21df71721845d0c44fe54b7f56b82995a9" + integrity sha512-4FRPXWETxtigtJW/gxzEDsX1LVbPAM93VleB83kZB+ellqbHMkyt2aJfuzNLRvFPnGi6bcE5SvfxgbXPeKteJw== fstream@^1.0.2: version "1.0.11" @@ -3515,10 +3452,10 @@ glob-parent@^3.0.0, glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.0.0.tgz#1dc99f0f39b006d3e92c2c284068382f0c20e954" - integrity sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg== +glob-parent@~5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" + integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== dependencies: is-glob "^4.0.1" @@ -3909,10 +3846,10 @@ gulp-symdest@^1.1.1: queue "^3.1.0" vinyl-fs "^2.4.3" -gulp-tsb@4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/gulp-tsb/-/gulp-tsb-4.0.4.tgz#c0486534e2f86cd4a11c2393593d9eae3a426ac4" - integrity sha512-BIIls2PpT3+JR1Svvd0SLjkBj2AmlHIRnum/ajf1XQUgbVA9wwsZuTjpVXU/K06KbTYfGsSSYAPRK2dICNAMZQ== +gulp-tsb@4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/gulp-tsb/-/gulp-tsb-4.0.5.tgz#2c4b41c5049de374934ca0fdb90dd713638af17a" + integrity sha512-zzjLZZBByrtljBhTbDBsScMVB10TYMXiMUV6FUKogs/hpHyTWdkqtMW/5nDPfsDA1xA+HFLgB19FAgWuPE0ZYw== dependencies: ansi-colors "^1.0.1" fancy-log "^1.3.2" @@ -4468,7 +4405,7 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-binary-path@^2.1.0: +is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== @@ -4598,7 +4535,7 @@ is-glob@^4.0.0: dependencies: is-extglob "^2.1.1" -is-glob@^4.0.1: +is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== @@ -6049,7 +5986,7 @@ normalize-path@^2.1.1: dependencies: remove-trailing-separator "^1.0.1" -normalize-path@^3.0.0: +normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== @@ -7387,10 +7324,10 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" -readdirp@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.1.2.tgz#fa85d2d14d4289920e4671dead96431add2ee78a" - integrity sha512-8rhl0xs2cxfVsqzreYCvs8EwBfn/DhVdqtoLmw19uI3SC5avYX9teCurlErfpPXGmYtMHReGaP2RsLnFvz/lnw== +readdirp@~3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" + integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ== dependencies: picomatch "^2.0.4" @@ -8831,11 +8768,6 @@ tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== -tslib@^1.9.3: - version "1.10.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" - integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== - tslint-microsoft-contrib@^6.0.0: version "6.1.1" resolved "https://registry.yarnpkg.com/tslint-microsoft-contrib/-/tslint-microsoft-contrib-6.1.1.tgz#1de9b5c2867f6cec762bab9d8e1619f2b8eb59fc" @@ -8946,10 +8878,10 @@ typescript-formatter@7.1.0: commandpost "^1.0.0" editorconfig "^0.15.0" -typescript@3.6: - version "3.6.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.2.tgz#105b0f1934119dde543ac8eb71af3a91009efe54" - integrity sha512-lmQ4L+J6mnu3xweP8+rOrUwzmN+MRAj7TgtJtDaXE5PMyX2kCrklhg3rvOsOIfNeAWMQWO2F1GPc1kMD2vLAfw== +typescript@3.7.0-dev.20191017: + version "3.7.0-dev.20191017" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.0-dev.20191017.tgz#e61440dd445edea6d7b9a699e7c5d5fbcd1906f2" + integrity sha512-Yi0lCPEN0cn9Gp8TEEkPpgKNR5SWAmx9Hmzzz+oEuivw6amURqRGynaLyFZkMA9iMsvYG5LLqhdlFO3uu5ZT/w== typescript@^2.6.2: version "2.6.2" @@ -9707,20 +9639,20 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" -xterm-addon-search@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.2.0.tgz#46659c7c33f9fc268ad3e7e6c5824bb2fdb65852" - integrity sha512-C/v2VvFn3hb1qUgjJPo7LxzxNCLBgNJv8n6v/bH2NqPz32/PNUF+IHu0SFf1TaIH+pydUpKXCtob5a/UyZg/+Q== +xterm-addon-search@0.3.0-beta5: + version "0.3.0-beta5" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.3.0-beta5.tgz#fd53d33a77a0235018479c712be8c12f7c0d083a" + integrity sha512-3GkGc4hST35/4hzgnQPLLvQ29WH7MkZ0mUrBE/Vm1IQum7TnMvWPTkGemwM+wAl4tdBmynNccHJlFeQzaQtVUg== -xterm-addon-web-links@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.2.0.tgz#b408a0be46211d8d4a0bb5e701d8f3c2bd07d473" - integrity sha512-dq81c4Pzli2PgKVBgY2REte9sCVibR3df8AP3SEvCTM9uYFnUFxtxzMTplPnc7+rXabVhFdbU6x+rstIk8HNQg== +xterm-addon-web-links@0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.2.1.tgz#6d1f2ce613e09870badf17615e7a1170a31542b2" + integrity sha512-2KnHtiq0IG7hfwv3jw2/jQeH1RBk2d5CH4zvgwQe00rLofSJqSfgnJ7gwowxxpGHrpbPr6Lv4AmH/joaNw2+HQ== -xterm@4.1.0-beta8: - version "4.1.0-beta8" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.1.0-beta8.tgz#c1ef323ba336d92f5b52302b66f672dfff75b3ef" - integrity sha512-6lf+XVv0qT285w49P92tSYoUB406jdbgdhnPKNzxCIGtGX8kcwK+pHZ8HncDwcEhmTmI4LZ/WXPGtOQJg+onwg== +xterm@^4.2.0-beta20: + version "4.2.0-beta20" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.2.0-beta20.tgz#74a759414511b4577159293397914ac33a2375d8" + integrity sha512-lH52ksaNQqWmLVV4OdLKWvhGkSRUXgJNvb2YYmVkBAm1PdVVS36ir8Qr4zYygnc2tBw689Wj65t4cNckmfpU1Q== y18n@^3.2.1: version "3.2.1"