From 7eba8c4c03fa6077f9f04d466b8df34da04354b9 Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Mon, 12 Aug 2019 21:31:51 -0700 Subject: [PATCH] Merge from vscode 52dcb723a39ae75bee1bd56b3312d7fcdc87aeed (#6719) --- .vscode/tasks.json | 5 +- .yarnrc | 2 +- .../darwin/product-build-darwin.yml | 3 +- .../linux/multiarch/arm64/build.sh | 3 + .../linux/multiarch/arm64/prebuild.sh | 3 + .../linux/multiarch/arm64/publish.sh | 3 + .../linux/product-build-linux-multiarch.yml | 5 +- .../linux/product-build-linux.yml | 3 +- build/azure-pipelines/product-build.yml | 14 + build/azure-pipelines/product-compile.yml | 7 +- build/azure-pipelines/upload-sourcemaps.js | 32 +- .../azure-pipelines/web/product-build-web.yml | 12 +- .../win32/product-build-win32.yml | 3 +- build/gulpfile.reh.js | 2 + build/gulpfile.vscode.js | 16 +- build/gulpfile.vscode.linux.js | 24 +- build/lib/extensions.js | 18 +- build/lib/extensions.ts | 19 +- build/lib/i18n.resources.json | 4 + build/npm/postinstall.js | 40 +- build/npm/update-grammar.js | 2 +- build/package.json | 2 +- build/yarn.lock | 18 +- cgmanifest.json | 4 +- extensions/git/package.json | 48 +- extensions/git/package.nls.json | 11 +- extensions/git/src/api/git.d.ts | 3 +- extensions/git/src/commands.ts | 133 ++- extensions/git/src/git.ts | 87 +- extensions/git/src/repository.ts | 95 +- extensions/git/src/statusbar.ts | 19 +- extensions/git/src/test/git.test.ts | 77 +- extensions/git/src/util.ts | 79 +- extensions/git/src/watch.ts | 25 + .../client/src/jsonMain.ts | 2 +- extensions/json/package.json | 5 +- .../syntaxes/markdown.tmLanguage.json | 2 +- extensions/package.json | 2 +- extensions/powershell/cgmanifest.json | 8 +- .../syntaxes/powershell.tmLanguage.json | 6 +- .../test/colorize-results/test_ps1.json | 6 +- .../theme-defaults/themes/dark_plus.json | 5 +- .../theme-defaults/themes/hc_black.json | 5 +- .../theme-defaults/themes/light_plus.json | 5 +- extensions/yarn.lock | 8 +- package.json | 12 +- remote/package.json | 7 +- remote/web/package.json | 1 + remote/web/yarn.lock | 68 ++ remote/yarn.lock | 94 +- resources/completions/bash/code | 4 +- resources/completions/zsh/_code | 4 +- resources/linux/rpm/code.spec.template | 12 +- src/buildfile.js | 18 +- src/main.js | 4 +- .../{node => common}/sqlExtHost.api.impl.ts | 35 +- .../browser/parts/views/customView.ts | 8 +- src/tsconfig.base.json | 1 - src/typings/applicationinsights-web.d.ts | 59 ++ src/typings/electron.d.ts | 2 +- src/typings/vscode-minimist.d.ts | 92 ++ src/vs/base/browser/browser.ts | 2 +- src/vs/base/browser/dom.ts | 6 +- src/vs/base/browser/touch.ts | 2 +- src/vs/base/browser/ui/actionbar/actionbar.ts | 4 +- .../ui/breadcrumbs/breadcrumbsWidget.ts | 4 +- .../browser/ui/centered/centeredViewLayout.ts | 11 +- .../base/browser/ui/checkbox/check-dark.svg | 3 + .../base/browser/ui/checkbox/check-light.svg | 3 + src/vs/base/browser/ui/checkbox/checkbox.css | 22 +- src/vs/base/browser/ui/checkbox/checkbox.ts | 53 +- .../browser/ui/contextview/contextview.ts | 19 +- .../base/browser/ui/countBadge/countBadge.ts | 2 +- src/vs/base/browser/ui/dialog/dialog.css | 7 +- src/vs/base/browser/ui/dialog/dialog.ts | 42 +- src/vs/base/browser/ui/dropdown/dropdown.ts | 8 +- src/vs/base/browser/ui/findinput/findInput.ts | 246 ++--- src/vs/base/browser/ui/grid/grid.ts | 55 +- src/vs/base/browser/ui/grid/gridview.ts | 50 +- src/vs/base/browser/ui/iconLabel/iconLabel.ts | 6 +- src/vs/base/browser/ui/inputbox/inputBox.css | 9 +- src/vs/base/browser/ui/inputbox/inputBox.ts | 57 +- src/vs/base/browser/ui/list/listPaging.ts | 2 +- src/vs/base/browser/ui/list/listView.ts | 13 +- src/vs/base/browser/ui/list/listWidget.ts | 4 +- src/vs/base/browser/ui/menu/menu.ts | 63 +- src/vs/base/browser/ui/menu/menubar.ts | 29 +- src/vs/base/browser/ui/sash/sash.ts | 2 +- .../browser/ui/scrollbar/abstractScrollbar.ts | 2 +- .../browser/ui/scrollbar/scrollableElement.ts | 16 +- src/vs/base/browser/ui/selectBox/selectBox.ts | 5 +- .../browser/ui/selectBox/selectBoxCustom.ts | 29 +- .../browser/ui/selectBox/selectBoxNative.ts | 2 +- src/vs/base/browser/ui/splitview/panelview.ts | 8 +- .../base/browser/ui/splitview/splitview.css | 2 +- src/vs/base/browser/ui/splitview/splitview.ts | 42 +- src/vs/base/browser/ui/toolbar/toolbar.ts | 5 +- src/vs/base/browser/ui/tree/abstractTree.ts | 17 +- src/vs/base/browser/ui/tree/asyncDataTree.ts | 3 +- .../browser/ui/tree/compressedObjectTree.ts | 2 +- src/vs/base/browser/ui/tree/dataTree.ts | 2 +- src/vs/base/browser/ui/tree/indexTree.ts | 2 +- src/vs/base/browser/ui/tree/objectTree.ts | 2 +- src/vs/base/common/actions.ts | 10 +- src/vs/base/common/color.ts | 4 +- src/vs/base/common/event.ts | 2 +- src/vs/base/common/history.ts | 4 +- src/vs/base/common/map.ts | 10 +- src/vs/base/common/path.ts | 2 + src/vs/base/node/encoding.ts | 12 +- src/vs/base/node/pfs.ts | 14 - src/vs/base/node/stream.ts | 109 ++- src/vs/base/parts/ipc/test/node/ipc.test.ts | 2 +- .../parts/quickopen/browser/quickOpenModel.ts | 6 +- src/vs/base/parts/tree/browser/treeModel.ts | 6 +- src/vs/base/parts/tree/browser/treeView.ts | 22 +- src/vs/base/test/common/utils.ts | 4 +- src/vs/base/test/node/pfs/pfs.test.ts | 26 - src/vs/code/browser/workbench/workbench.html | 3 - src/vs/code/browser/workbench/workbench.js | 3 +- .../issue/issueReporterMain.ts | 8 +- .../sharedProcess/sharedProcessMain.ts | 2 +- .../electron-browser/workbench/workbench.js | 6 +- src/vs/code/electron-main/sharedProcess.ts | 2 +- src/vs/code/electron-main/window.ts | 28 +- src/vs/code/electron-main/windows.ts | 16 +- .../test/electron-main/nativeHelpers.test.ts | 33 +- .../editor/browser/controller/mouseTarget.ts | 3 +- .../browser/controller/textAreaHandler.ts | 10 +- .../browser/controller/textAreaInput.ts | 1 + src/vs/editor/browser/core/editorState.ts | 9 + .../browser/services/codeEditorServiceImpl.ts | 6 +- src/vs/editor/browser/view/viewImpl.ts | 31 +- src/vs/editor/browser/view/viewLayer.ts | 4 +- .../contentWidgets/contentWidgets.ts | 20 +- .../viewParts/indentGuides/indentGuides.ts | 10 +- .../viewParts/lineNumbers/lineNumbers.ts | 12 +- .../browser/viewParts/lines/viewLines.ts | 8 + .../editor/browser/viewParts/margin/margin.ts | 25 +- .../browser/viewParts/minimap/minimap.ts | 6 +- .../overviewRuler/decorationsOverviewRuler.ts | 2 +- .../viewParts/viewCursors/viewCursor.ts | 8 +- .../viewParts/viewCursors/viewCursors.ts | 2 + .../editor/browser/widget/codeEditorWidget.ts | 2 +- .../editor/browser/widget/diffEditorWidget.ts | 118 +-- .../editor/common/commands/replaceCommand.ts | 5 +- src/vs/editor/common/commands/shiftCommand.ts | 5 +- .../commands/trimTrailingWhitespaceCommand.ts | 17 +- .../common/config/commonEditorConfig.ts | 7 +- src/vs/editor/common/config/editorOptions.ts | 12 +- .../common/controller/cursorTypeOperations.ts | 11 +- src/vs/editor/common/controller/oneCursor.ts | 4 +- src/vs/editor/common/diff/diffComputer.ts | 2 + .../editor/common/model/indentationGuesser.ts | 19 +- .../pieceTreeTextBuffer/pieceTreeBase.ts | 20 +- src/vs/editor/common/model/textModel.ts | 10 +- src/vs/editor/common/model/textModelTokens.ts | 5 +- src/vs/editor/common/modes.ts | 4 +- .../modes/languageConfigurationRegistry.ts | 12 +- .../common/modes/textToHtmlTokenizer.ts | 4 + .../services/editorWorkerServiceImpl.ts | 1 + .../editor/common/view/minimapCharRenderer.ts | 4 +- .../characterHardWrappingLineMapper.ts | 7 + .../common/viewModel/splitLinesCollection.ts | 6 +- .../common/viewModel/viewModelDecorations.ts | 3 +- .../caretOperations/moveCaretCommand.ts | 8 +- .../editor/contrib/codeAction/codeAction.ts | 16 +- .../contrib/codeAction/codeActionWidget.ts | 1 + .../codeAction/test/codeAction.test.ts | 6 +- .../contrib/codelens/codelensController.ts | 2 +- .../editor/contrib/codelens/codelensWidget.ts | 12 +- .../contrib/colorPicker/colorPickerWidget.ts | 6 +- .../contrib/comment/lineCommentCommand.ts | 6 +- .../editor/contrib/dnd/dragAndDropCommand.ts | 5 +- .../contrib/documentSymbols/outlineTree.ts | 19 +- src/vs/editor/contrib/find/findController.ts | 16 +- src/vs/editor/contrib/find/findWidget.ts | 48 +- .../editor/contrib/find/replaceAllCommand.ts | 5 +- .../contrib/find/test/findController.test.ts | 2 +- src/vs/editor/contrib/folding/folding.ts | 12 +- .../editor/contrib/folding/foldingRanges.ts | 3 +- .../goToDefinition/goToDefinitionMouse.ts | 8 +- .../contrib/gotoError/gotoErrorWidget.ts | 12 +- src/vs/editor/contrib/hover/hover.css | 5 +- src/vs/editor/contrib/hover/hover.ts | 24 +- src/vs/editor/contrib/hover/hoverWidgets.ts | 2 + .../editor/contrib/hover/modesContentHover.ts | 3 + .../editor/contrib/hover/modesGlyphHover.ts | 2 + .../contrib/inPlaceReplace/inPlaceReplace.ts | 4 +- .../editor/contrib/indentation/indentation.ts | 15 +- .../linesOperations/copyLinesCommand.ts | 8 +- .../linesOperations/moveLinesCommand.ts | 7 +- .../linesOperations/sortLinesCommand.ts | 5 +- src/vs/editor/contrib/links/links.ts | 6 +- .../contrib/parameterHints/parameterHints.css | 4 + .../parameterHints/parameterHintsWidget.ts | 19 +- .../contrib/referenceSearch/peekViewWidget.ts | 54 +- .../referenceSearch/referencesController.ts | 14 +- .../referenceSearch/referencesModel.ts | 2 +- .../referenceSearch/referencesWidget.ts | 27 +- .../editor/contrib/rename/renameInputField.ts | 34 +- .../contrib/snippet/snippetController2.ts | 2 +- .../editor/contrib/snippet/snippetParser.ts | 34 +- .../editor/contrib/snippet/snippetSession.ts | 24 +- .../snippet/test/snippetParser.test.ts | 15 +- .../editor/contrib/suggest/completionModel.ts | 20 +- .../editor/contrib/suggest/media/suggest.css | 6 +- src/vs/editor/contrib/suggest/suggest.ts | 2 +- .../contrib/suggest/suggestAlternatives.ts | 2 +- .../contrib/suggest/suggestController.ts | 14 + .../editor/contrib/suggest/suggestMemory.ts | 49 +- src/vs/editor/contrib/suggest/suggestModel.ts | 7 +- .../editor/contrib/suggest/suggestWidget.ts | 59 +- .../suggest/test/suggestMemory.test.ts | 23 + .../editor/contrib/suggest/wordContextKey.ts | 2 +- .../wordHighlighter/wordHighlighter.ts | 1 + .../editor/contrib/zoneWidget/zoneWidget.ts | 46 +- .../iPadShowKeyboard/iPadShowKeyboard.ts | 1 + .../browser/quickOpen/editorQuickOpen.ts | 6 +- .../standalone/browser/simpleServices.ts | 23 +- .../browser/standaloneCodeEditor.ts | 4 +- .../standalone/browser/standaloneLanguages.ts | 16 + .../browser/standaloneThemeServiceImpl.ts | 2 +- .../test/browser/controller/cursor.test.ts | 97 ++ .../test/common/model/textModel.test.ts | 40 + .../common/modes/textToHtmlTokenizer.test.ts | 104 ++- .../characterHardWrappingLineMapper.test.ts | 5 + src/vs/loader.js | 68 +- src/vs/monaco.d.ts | 16 + .../browser/menuEntryActionViewItem.ts | 4 +- src/vs/platform/actions/common/menuService.ts | 4 +- .../clipboard/browser/clipboardService.ts | 4 +- .../diagnostics/common/diagnosticsService.ts | 6 +- .../diagnostics/node/diagnosticsIpc.ts | 4 +- .../diagnostics/node/diagnosticsService.ts | 10 +- .../platform/dialogs/browser/dialogService.ts | 43 +- src/vs/platform/dialogs/common/dialogs.ts | 4 +- .../environment/common/environment.ts | 6 +- src/vs/platform/environment/node/argv.ts | 7 +- .../environment/node/environmentService.ts | 3 +- .../test/node/environmentService.test.ts | 11 + src/vs/platform/files/common/fileService.ts | 26 +- .../files/node/diskFileSystemProvider.ts | 152 +++- .../watcher/win32/csharpWatcherService.ts | 2 +- .../files/test/node/diskFileService.test.ts | 106 ++- .../electron-main/historyMainService.ts | 4 +- .../electron-browser/mainProcessService.ts | 2 +- .../electron-browser/sharedProcessService.ts | 2 +- .../issue/electron-browser/issueService.ts | 2 +- .../issue/electron-main/issueService.ts | 8 +- .../launch/electron-main/launchService.ts | 4 +- .../lifecycle/browser/lifecycleService.ts | 4 +- .../lifecycle/common/lifecycleService.ts | 2 +- .../electron-browser/lifecycleService.ts | 2 +- .../lifecycle/electron-main/lifecycleMain.ts | 2 +- src/vs/platform/list/browser/listService.ts | 4 +- .../electron-browser/localizationsService.ts | 2 +- .../localizations/node/localizations.ts | 4 +- .../electron-browser/menubarService.ts | 2 +- .../notification/common/notification.ts | 22 +- .../product/browser/productService.ts | 8 +- src/vs/platform/product/common/product.ts | 7 +- .../platform/product/node/productService.ts | 2 +- src/vs/platform/progress/common/progress.ts | 6 +- .../common/remoteAgentFileSystemChannel.ts | 2 +- .../platform/request/node/requestService.ts | 4 +- src/vs/platform/sign/browser/signService.ts | 2 +- src/vs/platform/sign/node/signService.ts | 6 +- .../storage/browser/storageService.ts | 19 +- src/vs/platform/storage/common/storage.ts | 15 +- .../storage/node/storageMainService.ts | 2 +- .../platform/storage/node/storageService.ts | 4 +- src/vs/platform/theme/common/colorRegistry.ts | 4 + src/vs/platform/theme/common/styler.ts | 10 +- .../update/electron-browser/updateService.ts | 2 +- .../electron-main/abstractUpdateService.ts | 11 +- .../update/node/update.config.contribution.ts | 3 +- src/vs/platform/url/common/urlService.ts | 2 +- src/vs/platform/windows/common/windows.ts | 4 +- .../windows/electron-main/windowsService.ts | 2 +- src/vs/vscode.d.ts | 9 +- src/vs/vscode.proposed.d.ts | 181 +--- .../api/browser/mainThreadCodeInsets.ts | 4 +- .../api/browser/mainThreadComments.ts | 15 +- .../api/browser/mainThreadDebugService.ts | 8 +- .../browser/mainThreadDocumentsAndEditors.ts | 4 +- .../workbench/api/browser/mainThreadEditor.ts | 7 +- .../api/browser/mainThreadEditors.ts | 4 +- .../api/browser/mainThreadLanguageFeatures.ts | 82 +- .../workbench/api/browser/mainThreadTask.ts | 32 +- .../api/browser/mainThreadTerminalService.ts | 101 +-- .../api/browser/mainThreadTreeViews.ts | 3 +- .../api/{node => common}/extHost.api.impl.ts | 130 ++- .../workbench/api/common/extHost.protocol.ts | 218 +++-- .../api/common/extHostApiCommands.ts | 23 +- .../workbench/api/common/extHostCodeInsets.ts | 2 +- .../workbench/api/common/extHostCommands.ts | 19 +- .../workbench/api/common/extHostComments.ts | 6 +- .../api/common/extHostConfiguration.ts | 18 +- .../api/common/extHostDebugService.ts | 34 + .../api/common/extHostDecorations.ts | 16 +- .../common/extHostDocumentContentProviders.ts | 2 +- .../api/common/extHostDocumentData.ts | 2 +- .../common/extHostDocumentSaveParticipant.ts | 6 +- .../api/common/extHostDocumentsAndEditors.ts | 17 +- .../api/common/extHostExtensionService.ts | 779 ++++++++++++++++ .../workbench/api/common/extHostFileSystem.ts | 2 +- .../common/extHostFileSystemEventService.ts | 4 +- .../api/common/extHostInitDataService.ts | 14 + .../api/common/extHostLanguageFeatures.ts | 86 +- src/vs/workbench/api/common/extHostMemento.ts | 8 +- src/vs/workbench/api/common/extHostOutput.ts | 89 +- .../workbench/api/common/extHostRpcService.ts | 29 + src/vs/workbench/api/common/extHostSCM.ts | 4 +- src/vs/workbench/api/common/extHostSearch.ts | 16 + src/vs/workbench/api/common/extHostStorage.ts | 11 +- .../api/common/extHostStoragePaths.ts | 16 + src/vs/workbench/api/common/extHostTask.ts | 30 + .../api/common/extHostTerminalService.ts | 32 + .../workbench/api/common/extHostTextEditor.ts | 10 +- .../workbench/api/common/extHostTreeViews.ts | 34 +- .../api/common/extHostTypeConverters.ts | 26 +- src/vs/workbench/api/common/extHostTypes.ts | 70 +- .../common/extHostUriTransformerService.ts | 37 + .../workbench/api/common/extHostWorkspace.ts | 30 +- src/vs/workbench/api/common/shared/tasks.ts | 8 +- src/vs/workbench/api/node/extHost.services.ts | 41 + src/vs/workbench/api/node/extHostCLIServer.ts | 6 +- .../workbench/api/node/extHostDebugService.ts | 47 +- .../api/node/extHostDownloadService.ts | 15 +- .../api/node/extHostExtensionService.ts | 838 +----------------- .../api/node/extHostOutputService.ts | 38 +- .../api/node/extHostRequireInterceptor.ts | 8 +- src/vs/workbench/api/node/extHostSearch.ts | 38 +- .../workbench/api/node/extHostStoragePaths.ts | 21 +- src/vs/workbench/api/node/extHostTask.ts | 231 +---- .../api/node/extHostTerminalService.ts | 368 +++----- src/vs/workbench/browser/actions.ts | 2 +- .../browser/actions/developerActions.ts | 124 ++- .../workbench/browser/actions/listCommands.ts | 53 +- .../browser/actions/media/screencast.css | 50 ++ .../browser/actions/windowActions.ts | 37 +- .../browser/actions/workspaceActions.ts | 21 +- src/vs/workbench/browser/composite.ts | 8 +- src/vs/workbench/browser/contextkeys.ts | 22 +- src/vs/workbench/browser/layout.ts | 467 +++++----- src/vs/workbench/browser/panel.ts | 2 +- .../parts/activitybar/activitybarPart.ts | 2 +- .../workbench/browser/parts/compositeBar.ts | 10 +- .../browser/parts/compositeBarActions.ts | 24 +- .../workbench/browser/parts/compositePart.ts | 10 +- .../browser/parts/editor/breadcrumbs.ts | 6 +- .../browser/parts/editor/breadcrumbsPicker.ts | 8 +- .../parts/editor/editor.contribution.ts | 3 +- .../workbench/browser/parts/editor/editor.ts | 11 +- .../browser/parts/editor/editorActions.ts | 16 + .../browser/parts/editor/editorControl.ts | 6 + .../browser/parts/editor/editorDropTarget.ts | 2 +- .../browser/parts/editor/editorGroupView.ts | 82 +- .../browser/parts/editor/editorPart.ts | 105 ++- .../notifications/notificationsCenter.ts | 2 +- .../notifications/notificationsStatus.ts | 6 +- .../notifications/notificationsViewer.ts | 16 +- .../browser/parts/panel/media/panelpart.css | 10 +- .../browser/parts/panel/panelPart.ts | 10 +- .../browser/parts/quickinput/quickInput.ts | 2 +- .../parts/quickinput/quickInputList.ts | 2 +- .../parts/quickopen/quickOpenController.ts | 4 +- .../browser/parts/sidebar/sidebarPart.ts | 7 +- .../browser/parts/statusbar/statusbarPart.ts | 2 +- .../browser/parts/titlebar/titlebarPart.ts | 4 +- .../browser/parts/views/customView.ts | 85 +- src/vs/workbench/browser/parts/views/views.ts | 2 +- src/vs/workbench/browser/viewlet.ts | 2 +- src/vs/workbench/browser/web.main.ts | 29 +- .../workbench/browser/web.simpleservices.ts | 151 +--- .../browser/workbench.contribution.ts | 4 +- src/vs/workbench/browser/workbench.ts | 15 +- src/vs/workbench/common/contributions.ts | 6 +- src/vs/workbench/common/editor.ts | 2 + .../common/editor/textEditorModel.ts | 4 +- src/vs/workbench/common/notifications.ts | 22 +- src/vs/workbench/common/views.ts | 3 +- .../backup/common/backupModelTracker.ts | 10 +- .../browser/callHierarchyPeek.ts | 18 +- .../browser/callHierarchyTree.ts | 6 +- .../browser/find/images/chevron-next-dark.svg | 3 + .../find/images/chevron-next-light.svg | 3 + .../find/images/chevron-previous-dark.svg | 3 + .../find/images/chevron-previous-light.svg | 3 + .../browser/find/images/close-dark.svg | 3 + .../browser/find/images/close-light.svg | 3 + .../browser}/find/simpleFindWidget.css | 0 .../browser}/find/simpleFindWidget.ts | 2 + .../browser/largeFileOptimizations.ts | 17 +- .../comments/browser/commentFormActions.ts | 4 +- .../comments/browser/commentGlyphWidget.ts | 2 +- .../contrib/comments/browser/commentNode.ts | 28 +- .../contrib/comments/browser/commentsPanel.ts | 41 +- .../comments/browser/commentsTreeViewer.ts | 192 ++-- .../comments/browser/reactionsAction.ts | 5 +- .../contrib/debug/browser/breakpointWidget.ts | 104 +-- .../contrib/debug/browser/breakpointsView.ts | 4 +- .../contrib/debug/browser/callStackView.ts | 16 +- .../debug/browser/debug.contribution.ts | 5 + .../debug/browser/debugActionViewItems.ts | 22 +- .../browser/debugConfigurationManager.ts | 15 +- .../debug/browser/debugEditorContribution.ts | 20 +- .../debug/browser/debugEditorModelManager.ts | 2 +- .../debug/browser/debugHelperService.ts | 21 - .../contrib/debug/browser/debugHover.ts | 23 +- .../contrib/debug/browser/debugQuickOpen.ts | 2 +- .../contrib/debug/browser/debugService.ts | 24 +- .../contrib/debug/browser/debugSession.ts | 9 +- .../contrib/debug/browser/debugStatus.ts | 2 +- .../contrib/debug/browser/debugToolBar.ts | 7 +- .../contrib/debug/browser/debugViewlet.ts | 8 +- .../contrib/debug/browser/exceptionWidget.ts | 8 +- .../browser/extensionHostDebugService.ts | 57 ++ .../debug/browser/loadedScriptsView.ts | 18 +- .../contrib/debug/browser/rawDebugSession.ts | 23 +- .../workbench/contrib/debug/browser/repl.ts | 22 +- .../debug/browser/statusbarColorProvider.ts | 2 +- .../contrib/debug/browser/variablesView.ts | 4 +- .../debug/browser/watchExpressionsView.ts | 4 +- .../debug/common/abstractDebugAdapter.ts | 6 +- .../workbench/contrib/debug/common/debug.ts | 12 +- .../contrib/debug/common/debugModel.ts | 6 +- .../extensionHostDebugService.ts | 0 .../contrib/debug/node/debugAdapter.ts | 10 +- .../contrib/debug/test/common/mockDebug.ts | 8 +- .../extensions/browser/extensionEditor.ts | 218 ++++- .../browser/media/extensionsViewlet.css | 4 + .../extensions/browser/media/markdown.css | 175 ---- .../extensionProfileService.ts | 6 +- .../electron-browser/extensionsSlowActions.ts | 6 +- .../contrib/feedback/browser/feedback.ts | 20 +- .../feedback/browser/feedbackStatusbarItem.ts | 18 +- .../browser/editors/fileEditorTracker.ts | 2 +- .../files/browser/editors/textFileEditor.ts | 2 +- .../contrib/files/browser/explorerViewlet.ts | 2 +- .../files/browser/fileActions.contribution.ts | 10 +- .../contrib/files/browser/fileActions.ts | 133 ++- .../contrib/files/browser/fileCommands.ts | 303 ++++--- .../files/browser/files.contribution.ts | 9 + .../contrib/files/browser/views/emptyView.ts | 6 +- .../files/browser/views/explorerView.ts | 9 +- .../files/browser/views/explorerViewer.ts | 8 +- .../files/browser/views/openEditorsView.ts | 24 +- .../contrib/files/common/dirtyFilesTracker.ts | 16 +- .../contrib/files/common/explorerModel.ts | 6 +- .../workbench/contrib/files/common/files.ts | 1 + .../test/electron-browser/fileActions.test.ts | 204 ++++- .../markers/browser/markersFileDecorations.ts | 4 +- .../markers/browser/markersTreeViewer.ts | 8 +- .../contrib/outline/browser/outlinePanel.ts | 16 +- .../contrib/output/browser/outputActions.ts | 6 +- .../contrib/output/browser/outputPanel.ts | 6 +- .../contrib/output/browser/outputServices.ts | 4 +- .../electron-browser/perfviewEditor.ts | 14 +- .../browser/media/settingsEditor2.css | 6 +- .../browser/media/settingsWidgets.css | 10 +- .../preferences/browser/settingsEditor2.ts | 16 +- .../preferences/browser/settingsLayout.ts | 5 + .../preferences/browser/settingsWidgets.ts | 10 +- .../quickopen/browser/gotoSymbolHandler.ts | 2 +- .../relauncher.contribution.ts | 100 ++- .../electron-browser/remote.contribution.ts | 2 +- .../browser/resourceServiceWorker.ts | 40 +- .../browser/resourceServiceWorkerClient.ts | 148 +--- .../browser/resourceServiceWorkerMain.ts | 2 +- .../contrib/scm/browser/dirtydiffDecorator.ts | 6 +- .../contrib/scm/browser/scm.contribution.ts | 23 +- .../contrib/scm/browser/scmActivity.ts | 118 ++- .../contrib/scm/browser/scmViewlet.ts | 44 +- .../contrib/search/browser/openFileHandler.ts | 6 +- .../search/browser/openSymbolHandler.ts | 2 +- .../contrib/search/common/searchModel.ts | 28 +- .../browser/snippetCompletionProvider.ts | 2 +- .../contrib/snippets/browser/tabCompletion.ts | 4 +- .../partsSplash.contribution.ts | 4 +- .../stats/electron-browser/workspaceStats.ts | 15 +- .../electron-browser/workspaceStatsService.ts | 53 +- .../tasks/browser/abstractTaskService.ts | 99 ++- .../contrib/tasks/browser/taskService.ts | 7 +- .../tasks/browser/terminalTaskSystem.ts | 36 +- .../contrib/tasks/common/problemMatcher.ts | 3 +- .../workbench/contrib/tasks/common/tasks.ts | 9 +- .../tasks/electron-browser/taskService.ts | 7 +- .../tasks/node/processRunnerDetector.ts | 14 +- .../contrib/tasks/node/processTaskSystem.ts | 4 +- .../commandTrackerAddon.ts} | 141 +-- .../browser/addons/navigationModeAddon.ts | 15 +- .../terminal/browser/terminal.contribution.ts | 19 +- .../contrib/terminal/browser/terminal.ts | 4 +- .../terminal/browser/terminalActions.ts | 12 +- .../terminal/browser/terminalConfigHelper.ts | 42 +- .../terminal/browser/terminalFindWidget.ts | 12 +- .../terminal/browser/terminalInstance.ts | 453 +++++----- .../browser/terminalInstanceService.ts | 12 +- .../terminal/browser/terminalLinkHandler.ts | 23 +- .../terminal/browser/terminalNativeService.ts | 5 +- .../contrib/terminal/browser/terminalPanel.ts | 69 +- .../browser/terminalProcessManager.ts | 32 +- .../terminal/browser/terminalService.ts | 13 +- .../contrib/terminal/browser/terminalTab.ts | 37 +- .../terminal/browser/terminalWidgetManager.ts | 12 +- .../contrib/terminal/common/terminal.ts | 42 +- .../terminal/common/terminalEnvironment.ts | 50 +- .../common/terminalProcessExtHostProxy.ts | 6 +- .../terminal/common/terminalService.ts | 8 +- .../terminalInstanceService.ts | 4 +- .../contrib/terminal/node/terminalProcess.ts | 29 +- .../terminal/node/windowsShellHelper.ts | 16 +- .../terminalCommandTracker.test.ts | 2 +- .../terminalLinkHandler.test.ts | 14 +- .../test/node/terminalEnvironment.test.ts | 44 + .../electron-browser/releaseNotesEditor.ts | 32 +- .../contrib/webview/browser/pre/index.html | 5 +- .../browser/webviewEditorInputFactory.ts | 4 +- .../webview/browser/webviewEditorService.ts | 3 + .../webview/browser/webviewFindWidget.ts | 6 +- .../contrib/webview/browser/webviewService.ts | 3 + .../electron-browser/webviewElement.ts | 2 +- .../welcome/overlay/browser/welcomeOverlay.ts | 6 +- .../electron-browser/actions/windowActions.ts | 28 +- .../electron-browser/main.contribution.ts | 53 +- src/vs/workbench/electron-browser/main.ts | 19 +- src/vs/workbench/electron-browser/window.ts | 46 +- .../node/accessibilityService.ts | 7 +- .../backup/common/backupFileService.ts | 64 +- .../services/backup/node/backupFileService.ts | 8 +- .../test/node/backupFileService.test.ts | 23 + .../bulkEdit/browser/bulkEditService.ts | 12 +- .../common/configurationEditingService.ts | 2 +- .../browser/configurationResolverService.ts | 2 +- .../common/configurationResolverSchema.ts | 16 +- .../configurationResolverService.test.ts | 2 +- .../dialogs/browser/fileDialogService.ts | 24 +- .../dialogs/browser/remoteFileDialog.ts | 30 +- .../services/editor/browser/editorService.ts | 17 +- .../editor/common/editorGroupsService.ts | 23 +- .../editor/test/browser/editorService.test.ts | 4 +- .../environment/browser/environmentService.ts | 9 +- .../environment/node/environmentService.ts | 4 +- .../node/extensionManagementService.ts | 6 +- .../common/abstractExtensionService.ts | 6 +- .../common/extensionDescriptionRegistry.ts | 6 +- .../{node => common}/extensionHostMain.ts | 61 +- .../common/extensionHostProcessManager.ts | 8 +- .../services/extensions/common/extensions.ts | 12 - .../common/inactiveExtensionUrlHandler.ts | 39 +- .../extensions/common/proxyIdentifier.ts | 2 +- .../common/remoteExtensionHostClient.ts | 1 + .../cachedExtensionScanner.ts | 4 +- .../electron-browser/extensionHost.ts | 2 + .../node/extensionHostProcessSetup.ts | 8 +- .../extensions/node/extensionPoints.ts | 4 +- .../extensions/test/node/rpcProtocol.test.ts | 4 +- .../services/history/browser/history.ts | 8 +- .../keybinding/browser/keybindingService.ts | 1 - .../keybinding.contribution.ts | 11 +- .../test/browserKeyboardMapper.test.ts | 4 +- .../mode/common/workbenchModeService.ts | 4 +- .../common/notificationService.ts | 84 +- .../output/common/outputChannelModel.ts | 4 +- .../output/node/outputChannelModelService.ts | 2 +- .../progress/browser/editorProgressService.ts | 2 +- .../progress/browser/progressService.ts | 2 +- .../progress/test/progressIndicator.test.ts | 10 +- .../common/abstractRemoteAgentService.ts | 1 + .../services/remote/node/tunnelService.ts | 3 + .../request/browser/requestService.ts | 6 +- .../services/search/common/search.ts | 13 +- .../services/search/common/searchService.ts | 4 +- .../telemetry/browser/telemetryService.ts | 73 +- .../textMate/common/TMScopeRegistry.ts | 2 +- .../electron-browser/textMateWorker.ts | 29 +- .../textfile/browser/textFileService.ts | 3 +- .../textfile/common/textFileEditorModel.ts | 18 +- .../common/textFileEditorModelManager.ts | 65 +- .../textfile/common/textFileService.ts | 157 ++-- .../common/textResourcePropertiesService.ts | 4 +- .../services/textfile/common/textfiles.ts | 8 +- .../services/textfile/node/textFileService.ts | 115 +-- .../textfile/test/textFileEditorModel.test.ts | 27 + .../textfile/test/textFileService.io.test.ts | 2 +- .../textfile/test/textFileService.test.ts | 27 +- .../timer/electron-browser/timerService.ts | 6 +- .../untitled/common/untitledEditorService.ts | 4 +- .../workspaceEditingService.ts | 34 +- src/vs/workbench/test/browser/part.test.ts | 8 +- .../test/common/notifications.test.ts | 4 +- .../api/extHostApiCommands.test.ts | 63 +- .../api/extHostCommands.test.ts | 10 +- .../api/extHostConfiguration.test.ts | 16 +- .../extHostDocumentSaveParticipant.test.ts | 12 +- .../api/extHostDocumentsAndEditors.test.ts | 7 +- .../api/extHostSearch.test.ts | 15 +- .../api/extHostTextEditors.test.ts | 6 +- .../api/extHostTreeViews.test.ts | 5 +- .../api/extHostWorkspace.test.ts | 65 +- .../electron-browser/api/testRPCProtocol.ts | 9 +- .../quickopen.perf.integrationTest.ts | 2 +- .../textsearch.perf.integrationTest.ts | 2 +- .../workbench/test/workbenchTestServices.ts | 8 +- src/vs/workbench/workbench.common.main.ts | 235 +++++ ...ch.main.css => workbench.desktop.main.css} | 0 ...n.nls.js => workbench.desktop.main.nls.js} | 0 ...ench.main.ts => workbench.desktop.main.ts} | 274 +----- src/vs/workbench/workbench.web.api.ts | 7 - src/vs/workbench/workbench.web.main.ts | 337 +------ test/smoke/package.json | 2 +- .../areas/workbench/data-migration.test.ts | 8 +- test/smoke/yarn.lock | 8 +- yarn.lock | 117 ++- 616 files changed, 9472 insertions(+), 7087 deletions(-) create mode 100755 build/azure-pipelines/linux/multiarch/arm64/build.sh create mode 100755 build/azure-pipelines/linux/multiarch/arm64/prebuild.sh create mode 100755 build/azure-pipelines/linux/multiarch/arm64/publish.sh create mode 100644 extensions/git/src/watch.ts rename src/sql/workbench/api/{node => common}/sqlExtHost.api.impl.ts (97%) create mode 100644 src/typings/applicationinsights-web.d.ts create mode 100644 src/typings/vscode-minimist.d.ts create mode 100644 src/vs/base/browser/ui/checkbox/check-dark.svg create mode 100644 src/vs/base/browser/ui/checkbox/check-light.svg rename src/vs/workbench/api/{node => common}/extHost.api.impl.ts (91%) create mode 100644 src/vs/workbench/api/common/extHostDebugService.ts create mode 100644 src/vs/workbench/api/common/extHostExtensionService.ts create mode 100644 src/vs/workbench/api/common/extHostInitDataService.ts create mode 100644 src/vs/workbench/api/common/extHostRpcService.ts create mode 100644 src/vs/workbench/api/common/extHostSearch.ts create mode 100644 src/vs/workbench/api/common/extHostStoragePaths.ts create mode 100644 src/vs/workbench/api/common/extHostTask.ts create mode 100644 src/vs/workbench/api/common/extHostTerminalService.ts create mode 100644 src/vs/workbench/api/common/extHostUriTransformerService.ts create mode 100644 src/vs/workbench/api/node/extHost.services.ts create mode 100644 src/vs/workbench/browser/actions/media/screencast.css create mode 100644 src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-next-dark.svg create mode 100644 src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-next-light.svg create mode 100644 src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-previous-dark.svg create mode 100644 src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-previous-light.svg create mode 100644 src/vs/workbench/contrib/codeEditor/browser/find/images/close-dark.svg create mode 100644 src/vs/workbench/contrib/codeEditor/browser/find/images/close-light.svg rename src/vs/{editor/contrib => workbench/contrib/codeEditor/browser}/find/simpleFindWidget.css (100%) rename src/vs/{editor/contrib => workbench/contrib/codeEditor/browser}/find/simpleFindWidget.ts (99%) delete mode 100644 src/vs/workbench/contrib/debug/browser/debugHelperService.ts create mode 100644 src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts rename src/vs/workbench/{services/extensions => contrib/debug}/electron-browser/extensionHostDebugService.ts (100%) delete mode 100644 src/vs/workbench/contrib/extensions/browser/media/markdown.css rename src/vs/workbench/contrib/relauncher/{electron-browser => common}/relauncher.contribution.ts (76%) rename src/vs/workbench/contrib/terminal/browser/{terminalCommandTracker.ts => addons/commandTrackerAddon.ts} (56%) rename src/vs/workbench/services/extensions/{node => common}/extensionHostMain.ts (71%) create mode 100644 src/vs/workbench/workbench.common.main.ts rename src/vs/workbench/{workbench.main.css => workbench.desktop.main.css} (100%) rename src/vs/workbench/{workbench.main.nls.js => workbench.desktop.main.nls.js} (100%) rename src/vs/workbench/{workbench.main.ts => workbench.desktop.main.ts} (64%) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 89aba8e5fe..5db8fb3fbc 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -81,6 +81,7 @@ "type": "gulp", "task": "hygiene", "problemMatcher": [] - } + }, + ] -} \ No newline at end of file +} diff --git a/.yarnrc b/.yarnrc index c45abdbaca..98712ee2b4 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "4.2.7" +target "4.2.9" runtime "electron" diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index b712e07284..c088bb8b45 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -2,11 +2,12 @@ steps: - script: | mkdir -p .build echo -n $BUILD_SOURCEVERSION > .build/commit + echo -n $VSCODE_QUALITY > .build/quality displayName: Prepare cache flag - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 inputs: - keyfile: 'build/.cachesalt, .build/commit' + keyfile: 'build/.cachesalt, .build/commit, .build/quality' targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' vstsFeed: 'npm-vscode' platformIndependent: true diff --git a/build/azure-pipelines/linux/multiarch/arm64/build.sh b/build/azure-pipelines/linux/multiarch/arm64/build.sh new file mode 100755 index 0000000000..4f01bc9a7e --- /dev/null +++ b/build/azure-pipelines/linux/multiarch/arm64/build.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -e +echo 'noop' \ No newline at end of file diff --git a/build/azure-pipelines/linux/multiarch/arm64/prebuild.sh b/build/azure-pipelines/linux/multiarch/arm64/prebuild.sh new file mode 100755 index 0000000000..4f01bc9a7e --- /dev/null +++ b/build/azure-pipelines/linux/multiarch/arm64/prebuild.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -e +echo 'noop' \ No newline at end of file diff --git a/build/azure-pipelines/linux/multiarch/arm64/publish.sh b/build/azure-pipelines/linux/multiarch/arm64/publish.sh new file mode 100755 index 0000000000..4f01bc9a7e --- /dev/null +++ b/build/azure-pipelines/linux/multiarch/arm64/publish.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -e +echo 'noop' \ No newline at end of file diff --git a/build/azure-pipelines/linux/product-build-linux-multiarch.yml b/build/azure-pipelines/linux/product-build-linux-multiarch.yml index dff5ae2cea..d36c115be7 100644 --- a/build/azure-pipelines/linux/product-build-linux-multiarch.yml +++ b/build/azure-pipelines/linux/product-build-linux-multiarch.yml @@ -2,11 +2,12 @@ steps: - script: | mkdir -p .build echo -n $BUILD_SOURCEVERSION > .build/commit + echo -n $VSCODE_QUALITY > .build/quality displayName: Prepare cache flag - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 inputs: - keyfile: 'build/.cachesalt, .build/commit' + keyfile: 'build/.cachesalt, .build/commit, .build/quality' targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' vstsFeed: 'npm-vscode' platformIndependent: true @@ -112,4 +113,4 @@ steps: - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 displayName: 'Component Detection' - continueOnError: true \ No newline at end of file + continueOnError: true diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index dee07a95d7..a6cc1c3496 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -2,11 +2,12 @@ steps: - script: | mkdir -p .build echo -n $BUILD_SOURCEVERSION > .build/commit + echo -n $VSCODE_QUALITY > .build/quality displayName: Prepare cache flag - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 inputs: - keyfile: 'build/.cachesalt, .build/commit' + keyfile: 'build/.cachesalt, .build/commit, .build/quality' targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' vstsFeed: 'npm-vscode' platformIndependent: true diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index ad437466da..f38f431182 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -66,6 +66,17 @@ jobs: steps: - template: linux/product-build-linux-multiarch.yml +- job: LinuxArm64 + condition: and(succeeded(), eq(variables['VSCODE_COMPILE_ONLY'], 'false'), eq(variables['VSCODE_BUILD_LINUX_ARM64'], 'true'), ne(variables['VSCODE_QUALITY'], 'stable')) + pool: + vmImage: 'Ubuntu-16.04' + variables: + VSCODE_ARCH: arm64 + dependsOn: + - Compile + steps: + - template: linux/product-build-linux-multiarch.yml + - job: LinuxAlpine condition: and(succeeded(), eq(variables['VSCODE_COMPILE_ONLY'], 'false'), eq(variables['VSCODE_BUILD_LINUX_ALPINE'], 'true'), ne(variables['VSCODE_QUALITY'], 'stable')) pool: @@ -128,6 +139,9 @@ jobs: steps: - template: sync-mooncake.yml +trigger: none +pr: none + schedules: - cron: "0 5 * * Mon-Fri" displayName: Mon-Fri at 7:00 diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index e5105326e7..169dfe154f 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -2,11 +2,12 @@ steps: - script: | mkdir -p .build echo -n $BUILD_SOURCEVERSION > .build/commit + echo -n $VSCODE_QUALITY > .build/quality displayName: Prepare cache flag - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 inputs: - keyfile: 'build/.cachesalt, .build/commit' + keyfile: 'build/.cachesalt, .build/commit, .build/quality' targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' vstsFeed: 'npm-vscode' platformIndependent: true @@ -116,9 +117,9 @@ steps: - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 inputs: - keyfile: 'build/.cachesalt, .build/commit' + keyfile: 'build/.cachesalt, .build/commit, .build/quality' targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' vstsFeed: 'npm-vscode' platformIndependent: true alias: 'Compilation' - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) \ No newline at end of file + condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) diff --git a/build/azure-pipelines/upload-sourcemaps.js b/build/azure-pipelines/upload-sourcemaps.js index 6d52a0a28b..6980e29569 100644 --- a/build/azure-pipelines/upload-sourcemaps.js +++ b/build/azure-pipelines/upload-sourcemaps.js @@ -13,19 +13,37 @@ const util = require('../lib/util'); const root = path.dirname(path.dirname(__dirname)); const commit = util.getVersion(root); -function main() { - const vs = vfs.src('out-vscode-min/**/*.map', { base: 'out-vscode-min' }) // client source-maps only +// optionally allow to pass in explicit base/maps to upload +const [, , base, maps] = process.argv; + +const fetch = function (base, maps = `${base}/**/*.map`) { + return vfs.src(maps, { base }) .pipe(es.mapSync(f => { f.path = `${f.base}/core/${f.relative}`; return f; })); +}; - const extensionsOut = vfs.src(['.build/extensions/**/*.js.map', '!**/node_modules/**'], { base: '.build' }); +function main() { + const sources = []; - return es.merge(vs, extensionsOut) + // vscode client maps (default) + if (!base) { + const vs = fetch('out-vscode-min'); // client source-maps only + sources.push(vs); + + const extensionsOut = vfs.src(['.build/extensions/**/*.js.map', '!**/node_modules/**'], { base: '.build' }); + sources.push(extensionsOut); + } + + // specific client base/maps + else { + sources.push(fetch(base, maps)); + } + + return es.merge(...sources) .pipe(es.through(function (data) { - // debug - console.log('Uploading Sourcemap', data.relative); + console.log('Uploading Sourcemap', data.relative); // debug this.emit('data', data); })) .pipe(azure.upload({ @@ -36,4 +54,4 @@ function main() { })); } -main(); \ No newline at end of file +main(); diff --git a/build/azure-pipelines/web/product-build-web.yml b/build/azure-pipelines/web/product-build-web.yml index 7c65dc982f..fe5231dacc 100644 --- a/build/azure-pipelines/web/product-build-web.yml +++ b/build/azure-pipelines/web/product-build-web.yml @@ -2,11 +2,12 @@ steps: - script: | mkdir -p .build echo -n $BUILD_SOURCEVERSION > .build/commit + echo -n $VSCODE_QUALITY > .build/quality displayName: Prepare cache flag - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 inputs: - keyfile: 'build/.cachesalt, .build/commit' + keyfile: 'build/.cachesalt, .build/commit, .build/quality' targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' vstsFeed: 'npm-vscode' platformIndependent: true @@ -87,6 +88,15 @@ steps: yarn gulp vscode-web-min-ci displayName: Build + # upload only the workbench.web.api.js source maps because + # we just compiled these bits in the previous step and the + # general task to upload source maps has already been run +- script: | + set -e + AZURE_STORAGE_ACCESS_KEY="$(ticino-storage-key)" \ + node build/azure-pipelines/upload-sourcemaps out-vscode-web-min out-vscode-web-min/vs/workbench/workbench.web.api.js.map + displayName: Upload sourcemaps (Web) + - script: | set -e AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 7a72dcfb09..71a47e63d4 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -2,11 +2,12 @@ steps: - powershell: | mkdir .build -ea 0 "$env:BUILD_SOURCEVERSION" | Out-File -Encoding ascii -NoNewLine .build\commit + "$env:VSCODE_QUALITY" | Out-File -Encoding ascii -NoNewLine .build\quality displayName: Prepare cache flag - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 inputs: - keyfile: 'build/.cachesalt, .build/commit' + keyfile: 'build/.cachesalt, .build/commit, .build/quality' targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' vstsFeed: 'npm-vscode' platformIndependent: true diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js index c5c4dbf66f..80309a2bc8 100644 --- a/build/gulpfile.reh.js +++ b/build/gulpfile.reh.js @@ -31,6 +31,7 @@ const BUILD_TARGETS = [ { platform: 'linux', arch: 'ia32', pkgTarget: 'node8-linux-x86' }, { platform: 'linux', arch: 'x64', pkgTarget: 'node8-linux-x64' }, { platform: 'linux', arch: 'armhf', pkgTarget: 'node8-linux-armv7' }, + { platform: 'linux', arch: 'arm64', pkgTarget: 'node8-linux-arm64' }, { platform: 'linux', arch: 'alpine', pkgTarget: 'node8-linux-alpine' }, ]; @@ -41,6 +42,7 @@ gulp.task('vscode-reh-win32-x64-min', noop); gulp.task('vscode-reh-darwin-min', noop); gulp.task('vscode-reh-linux-x64-min', noop); gulp.task('vscode-reh-linux-armhf-min', noop); +gulp.task('vscode-reh-linux-arm64-min', noop); gulp.task('vscode-reh-linux-alpine-min', noop); gulp.task('vscode-reh-web-win32-ia32-min', noop); diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index b99ead1362..ee01094995 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -58,7 +58,7 @@ const nodeModules = [ // Build const vscodeEntryPoints = _.flatten([ - buildfile.entrypoint('vs/workbench/workbench.main'), + buildfile.entrypoint('vs/workbench/workbench.desktop.main'), buildfile.base, buildfile.serviceWorker, buildfile.workbench, @@ -255,8 +255,8 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op const out = sourceFolderName; const checksums = computeChecksums(out, [ - 'vs/workbench/workbench.main.js', - 'vs/workbench/workbench.main.css', + 'vs/workbench/workbench.desktop.main.js', + 'vs/workbench/workbench.desktop.main.css', 'vs/code/electron-browser/workbench/workbench.html', 'vs/code/electron-browser/workbench/workbench.js' ]); @@ -353,7 +353,15 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op .pipe(electron(_.extend({}, config, { platform, arch, ffmpegChromium: true }))) .pipe(filter(['**', '!LICENSE', '!LICENSES.chromium.html', '!version'], { dot: true })); - // result = es.merge(result, gulp.src('resources/completions/**', { base: '.' })); + if (platform === 'linux') { + result = es.merge(result, gulp.src('resources/completions/bash/code', { base: '.' }) + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename(function (f) { f.basename = product.applicationName; }))); + + result = es.merge(result, gulp.src('resources/completions/zsh/_code', { base: '.' }) + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename(function (f) { f.basename = '_' + product.applicationName; }))); + } if (platform === 'win32') { result = es.merge(result, gulp.src('resources/win32/bin/code.js', { base: 'resources/win32', allowEmpty: true })); diff --git a/build/gulpfile.vscode.linux.js b/build/gulpfile.vscode.linux.js index 7cd31a1a18..752f9a19ff 100644 --- a/build/gulpfile.vscode.linux.js +++ b/build/gulpfile.vscode.linux.js @@ -55,11 +55,13 @@ function prepareDebPackage(arch) { const icon = gulp.src('resources/linux/code.png', { base: '.' }) .pipe(rename('usr/share/pixmaps/' + product.linuxIconName + '.png')); - // const bash_completion = gulp.src('resources/completions/bash/code') - // .pipe(rename('usr/share/bash-completion/completions/code')); + const bash_completion = gulp.src('resources/completions/bash/code') + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename('usr/share/bash-completion/completions/' + product.applicationName)); - // const zsh_completion = gulp.src('resources/completions/zsh/_code') - // .pipe(rename('usr/share/zsh/vendor-completions/_code')); + const zsh_completion = gulp.src('resources/completions/zsh/_code') + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename('usr/share/zsh/vendor-completions/_' + product.applicationName)); const code = gulp.src(binaryDir + '/**/*', { base: binaryDir }) .pipe(rename(function (p) { p.dirname = 'usr/share/' + product.applicationName + '/' + p.dirname; })); @@ -95,7 +97,7 @@ function prepareDebPackage(arch) { .pipe(replace('@@UPDATEURL@@', product.updateUrl || '@@UPDATEURL@@')) .pipe(rename('DEBIAN/postinst')); - const all = es.merge(control, postinst, postrm, prerm, desktops, appdata, icon, /* bash_completion, zsh_completion, */ code); + const all = es.merge(control, postinst, postrm, prerm, desktops, appdata, icon, bash_completion, zsh_completion, code); return all.pipe(vfs.dest(destination)); }; @@ -146,11 +148,13 @@ function prepareRpmPackage(arch) { const icon = gulp.src('resources/linux/code.png', { base: '.' }) .pipe(rename('BUILD/usr/share/pixmaps/' + product.linuxIconName + '.png')); - // const bash_completion = gulp.src('resources/completions/bash/code') - // .pipe(rename('BUILD/usr/share/bash-completion/completions/code')); + const bash_completion = gulp.src('resources/completions/bash/code') + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename('BUILD/usr/share/bash-completion/completions/' + product.applicationName)); - // const zsh_completion = gulp.src('resources/completions/zsh/_code') - // .pipe(rename('BUILD/usr/share/zsh/site-functions/_code')); + const zsh_completion = gulp.src('resources/completions/zsh/_code') + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename('BUILD/usr/share/zsh/site-functions/_' + product.applicationName)); const code = gulp.src(binaryDir + '/**/*', { base: binaryDir }) .pipe(rename(function (p) { p.dirname = 'BUILD/usr/share/' + product.applicationName + '/' + p.dirname; })); @@ -173,7 +177,7 @@ function prepareRpmPackage(arch) { const specIcon = gulp.src('resources/linux/rpm/code.xpm', { base: '.' }) .pipe(rename('SOURCES/' + product.applicationName + '.xpm')); - const all = es.merge(code, desktops, appdata, icon, /* bash_completion, zsh_completion, */ spec, specIcon); + const all = es.merge(code, desktops, appdata, icon, bash_completion, zsh_completion, spec, specIcon); return all.pipe(vfs.dest(getRpmBuildPath(rpmArch))); }; diff --git a/build/lib/extensions.js b/build/lib/extensions.js index 9aa43bc202..bd53b4a93e 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -29,12 +29,18 @@ const commit = util.getVersion(root); const sourceMappingURLBase = `https://ticino.blob.core.windows.net/sourcemaps/${commit}`; function fromLocal(extensionPath) { const webpackFilename = path.join(extensionPath, 'extension.webpack.config.js'); - if (fs.existsSync(webpackFilename)) { - return fromLocalWebpack(extensionPath); - } - else { - return fromLocalNormal(extensionPath); - } + const input = fs.existsSync(webpackFilename) + ? fromLocalWebpack(extensionPath) + : fromLocalNormal(extensionPath); + const tmLanguageJsonFilter = filter('**/*.tmLanguage.json', { restore: true }); + return input + .pipe(tmLanguageJsonFilter) + .pipe(buffer()) + .pipe(es.mapSync((f) => { + f.contents = Buffer.from(JSON.stringify(JSON.parse(f.contents.toString('utf8')))); + return f; + })) + .pipe(tmLanguageJsonFilter.restore); } function fromLocalWebpack(extensionPath) { const result = es.through(); diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index d05ad07029..1acf3bd507 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -30,11 +30,20 @@ const sourceMappingURLBase = `https://ticino.blob.core.windows.net/sourcemaps/${ function fromLocal(extensionPath: string): Stream { const webpackFilename = path.join(extensionPath, 'extension.webpack.config.js'); - if (fs.existsSync(webpackFilename)) { - return fromLocalWebpack(extensionPath); - } else { - return fromLocalNormal(extensionPath); - } + const input = fs.existsSync(webpackFilename) + ? fromLocalWebpack(extensionPath) + : fromLocalNormal(extensionPath); + + const tmLanguageJsonFilter = filter('**/*.tmLanguage.json', { restore: true }); + + return input + .pipe(tmLanguageJsonFilter) + .pipe(buffer()) + .pipe(es.mapSync((f: File) => { + f.contents = Buffer.from(JSON.stringify(JSON.parse(f.contents.toString('utf8')))); + return f; + })) + .pipe(tmLanguageJsonFilter.restore); } function fromLocalWebpack(extensionPath: string): Stream { diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 32b0e6fb4c..570d97669c 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -269,6 +269,10 @@ { "name": "vs/workbench/services/preferences", "project": "vscode-preferences" + }, + { + "name": "vs/workbench/services/notification", + "project": "vscode-workbench" } ] } diff --git a/build/npm/postinstall.js b/build/npm/postinstall.js index 569a111009..0feda17b74 100644 --- a/build/npm/postinstall.js +++ b/build/npm/postinstall.js @@ -78,4 +78,42 @@ const processTreeDts = path.join('node_modules', 'windows-process-tree', 'typing if (fs.existsSync(processTreeDts)) { console.log('Removing windows-process-tree.d.ts'); fs.unlinkSync(processTreeDts); -} \ No newline at end of file +} + +function getInstalledVersion(packageName, cwd) { + const opts = {}; + if (cwd) { + opts.cwd = cwd; + } + + const result = cp.spawnSync(yarn, ['list', '--pattern', packageName], opts); + const stdout = result.stdout.toString(); + const match = stdout.match(new RegExp(packageName + '@(\\S+)')); + if (!match || !match[1]) { + throw new Error('Unexpected output from yarn list: ' + stdout); + } + + return match[1]; +} + +function assertSameVersionsBetweenFolders(packageName, otherFolder) { + const baseVersion = getInstalledVersion(packageName); + const otherVersion = getInstalledVersion(packageName, otherFolder); + + if (baseVersion !== otherVersion) { + throw new Error(`Mismatched versions installed for ${packageName}: root has ${baseVersion}, ./${otherFolder} has ${otherVersion}. These should be the same!`); + } +} + +// Check that modules in both the base package.json and remote/ have the same version installed +const requireSameVersionsInRemote = [ + 'xterm', + 'xterm-addon-search', + 'xterm-addon-web-links', + 'node-pty', + 'vscode-ripgrep' +]; + +requireSameVersionsInRemote.forEach(packageName => { + assertSameVersionsBetweenFolders(packageName, 'remote'); +}); diff --git a/build/npm/update-grammar.js b/build/npm/update-grammar.js index 87b37a92a3..b9a523daa6 100644 --- a/build/npm/update-grammar.js +++ b/build/npm/update-grammar.js @@ -132,7 +132,7 @@ exports.update = function (repoId, repoPath, dest, modifyGrammar, version = 'mas if (packageJsonPathOverride) { packageJsonPath += packageJsonPathOverride; } - packageJsonPath += '/package.json'; + packageJsonPath += 'package.json'; for (let i = 0; i < cgmanifestRead.registrations.length; i++) { if (cgmanifestRead.registrations[i].component.git.repositoryUrl.substr(cgmanifestRead.registrations[i].component.git.repositoryUrl.length - repoId.length, repoId.length) === repoId) { cgmanifestRead.registrations[i].component.git.commitHash = info.commitSha; diff --git a/build/package.json b/build/package.json index 6fa4be7b5d..d1edeb7624 100644 --- a/build/package.json +++ b/build/package.json @@ -44,7 +44,7 @@ "service-downloader": "github:anthonydresser/service-downloader#0.1.5", "typescript": "3.5.2", "vsce": "1.48.0", - "vscode-telemetry-extractor": "1.5.3", + "vscode-telemetry-extractor": "^1.5.4", "xml2js": "^0.4.17" }, "scripts": { diff --git a/build/yarn.lock b/build/yarn.lock index 17e861f0e1..75e3562368 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -3618,19 +3618,19 @@ vsce@1.48.0: yauzl "^2.3.1" yazl "^2.2.2" -vscode-ripgrep@^1.5.5: - version "1.5.5" - resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.5.tgz#24c0e9cb356cf889c98e15ecb58f9cf654a1d961" - integrity sha512-OrPrAmcun4+uZAuNcQvE6CCPskh+5AsjANod/Q3zRcJcGNxgoOSGlQN9RPtatkUNmkN8Nn8mZBnS1jMylu/dKg== +vscode-ripgrep@^1.5.6: + version "1.5.6" + resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.6.tgz#93bf5c99ca5f8248950a305e224f6ca153c30af4" + integrity sha512-WRIM9XpUj6dsfdAmuI3ANbmT1ysPUVsYy/2uCLDHJa9kbiB4T7uGvFnnc0Rgx2qQnyRAwL7PeWaFgUljPPxf2g== -vscode-telemetry-extractor@1.5.3: - version "1.5.3" - resolved "https://registry.yarnpkg.com/vscode-telemetry-extractor/-/vscode-telemetry-extractor-1.5.3.tgz#c17f9065a47425edafd23ea161e80c23274e009d" - integrity sha512-feioJ1e1KyMa9rzblnLbSOduo+Ny0l62H3/bSDgfgCSnU/km+tTSYxPBvZHVr7iQfQGC95J61yC/ObqS9EbaQg== +vscode-telemetry-extractor@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/vscode-telemetry-extractor/-/vscode-telemetry-extractor-1.5.4.tgz#bcb0d17667fa1b77715e3a3bf372ade18f846782" + integrity sha512-MN9LNPo0Rc6cy3sIWTAG97PTWkEKdRnP0VeYoS8vjKSNtG9CAsrUxHgFfYoHm2vNK/ijd0a4NzETyVGO2kT6hw== dependencies: command-line-args "^5.1.1" ts-morph "^3.1.3" - vscode-ripgrep "^1.5.5" + vscode-ripgrep "^1.5.6" vso-node-api@6.1.2-preview: version "6.1.2-preview" diff --git a/cgmanifest.json b/cgmanifest.json index 638303702a..5ba2e16626 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "36ea114ac0616e469e75ae94e6d53af48925e036" + "commitHash": "3d4d6454007f14fa9a5f0e1fa49206fb91b676cc" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "4.2.7" + "version": "4.2.9" }, { "component": { diff --git a/extensions/git/package.json b/extensions/git/package.json index 4a789aaa6a..ffa34ca6ef 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1148,6 +1148,26 @@ "description": "%config.enableSmartCommit%", "default": false }, + "git.smartCommitChanges": { + "type": "string", + "enum": [ + "all", + "tracked" + ], + "enumDescriptions": [ + "%config.smartCommitChanges.all%", + "%config.smartCommitChanges.tracked%" + ], + "scope": "resource", + "description": "%config.smartCommitChanges%", + "default": "all" + }, + "git.suggestSmartCommit": { + "type": "boolean", + "scope": "resource", + "description": "%config.suggestSmartCommit%", + "default": true + }, "git.enableCommitSigning": { "type": "boolean", "scope": "resource", @@ -1165,10 +1185,26 @@ "default": true, "description": "%config.decorations.enabled%" }, - "git.promptToSaveFilesBeforeCommit": { + "git.enableStatusBarSync": { "type": "boolean", - "scope": "resource", "default": true, + "description": "%config.enableStatusBarSync%", + "scope": "resource" + }, + "git.promptToSaveFilesBeforeCommit": { + "type": "string", + "enum": [ + "always", + "staged", + "never" + ], + "enumDescriptions": [ + "%config.promptToSaveFilesBeforeCommit.always%", + "%config.promptToSaveFilesBeforeCommit.staged%", + "%config.promptToSaveFilesBeforeCommit.never%" + ], + "scope": "resource", + "default": "always", "description": "%config.promptToSaveFilesBeforeCommit%" }, "git.postCommitCommand": { @@ -1312,6 +1348,12 @@ "scope": "resource", "default": true, "description": "%config.openDiffOnClick%" + }, + "git.supportCancellation": { + "type": "boolean", + "scope": "resource", + "default": false, + "description": "%config.supportCancellation%" } } }, @@ -1477,4 +1519,4 @@ "@types/which": "^1.0.28", "mocha": "^3.2.0" } -} \ No newline at end of file +} diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index adff0134ed..367544c6cc 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -89,10 +89,18 @@ "config.ignoreLimitWarning": "Ignores the warning when there are too many changes in a repository.", "config.defaultCloneDirectory": "The default location to clone a git repository.", "config.enableSmartCommit": "Commit all changes when there are no staged changes.", + "config.smartCommitChanges": "Control which changes are automatically staged by Smart Commit.", + "config.smartCommitChanges.all": "Automatically stage all changes.", + "config.smartCommitChanges.tracked": "Automatically staged tracked changes only.", + "config.suggestSmartCommit": "Suggests to enable smart commit (commit all changes when there are no staged changes).", "config.enableCommitSigning": "Enables commit signing with GPG.", "config.discardAllScope": "Controls what changes are discarded by the `Discard all changes` command. `all` discards all changes. `tracked` discards only tracked files. `prompt` shows a prompt dialog every time the action is run.", "config.decorations.enabled": "Controls whether Git contributes colors and badges to the explorer and the open editors view.", + "config.enableStatusBarSync": "Controls whether the Git Sync command appears in the status bar.", "config.promptToSaveFilesBeforeCommit": "Controls whether Git should check for unsaved files before committing.", + "config.promptToSaveFilesBeforeCommit.always": "Check for any unsaved files.", + "config.promptToSaveFilesBeforeCommit.staged": "Check only for unsaved staged files.", + "config.promptToSaveFilesBeforeCommit.never": "Disable this check.", "config.postCommitCommand": "Runs a git command after a successful commit.", "config.postCommitCommand.none": "Don't run any command after a commit.", "config.postCommitCommand.push": "Run 'Git Push' after a successful commit.", @@ -118,6 +126,7 @@ "config.useForcePushWithLease": "Controls whether force pushing uses the safer force-with-lease variant.", "config.confirmForcePush": "Controls whether to ask for confirmation before force-pushing.", "config.openDiffOnClick": "Controls whether the diff editor should be opened when clicking a change. Otherwise the regular editor will be opened.", + "config.supportCancellation": "Controls whether a notification comes up when running the Sync action, which allows the user to cancel the operation.", "colors.added": "Color for added resources.", "colors.modified": "Color for modified resources.", "colors.deleted": "Color for deleted resources.", @@ -125,4 +134,4 @@ "colors.ignored": "Color for ignored resources.", "colors.conflict": "Color for resources with conflicts.", "colors.submodule": "Color for submodule resources." -} \ No newline at end of file +} diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index e4d0510675..e8362f68c5 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -238,5 +238,6 @@ export const enum GitErrorCodes { CantLockRef = 'CantLockRef', CantRebaseMultipleBranches = 'CantRebaseMultipleBranches', PatchDoesNotApply = 'PatchDoesNotApply', - NoPathFound = 'NoPathFound' + NoPathFound = 'NoPathFound', + UnknownPath = 'UnknownPath', } diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 27bbef5eca..6b51af28b6 100755 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -353,9 +353,11 @@ export class CommandCenter { switch (resource.type) { case Status.INDEX_MODIFIED: case Status.INDEX_RENAMED: + case Status.INDEX_ADDED: return this.getURI(resource.original, 'HEAD'); case Status.MODIFIED: + case Status.UNTRACKED: return this.getURI(resource.resourceUri, '~'); case Status.DELETED_BY_THEM: @@ -414,6 +416,7 @@ export class CommandCenter { switch (resource.type) { case Status.INDEX_MODIFIED: case Status.INDEX_RENAMED: + case Status.INDEX_ADDED: return `${basename} (Index)`; case Status.MODIFIED: @@ -426,6 +429,10 @@ export class CommandCenter { case Status.DELETED_BY_THEM: return `${basename} (Ours)`; + + case Status.UNTRACKED: + + return `${basename} (Untracked)`; } return ''; @@ -450,6 +457,8 @@ export class CommandCenter { return; } + url = url.trim().replace(/^git\s+clone\s+/, ''); + const config = workspace.getConfiguration('git'); let defaultCloneDirectory = config.get('defaultCloneDirectory') || os.homedir(); defaultCloneDirectory = defaultCloneDirectory.replace(/^~/, os.homedir()); @@ -698,7 +707,13 @@ export class CommandCenter { viewColumn: ViewColumn.Active }; - const document = await workspace.openTextDocument(uri); + let document; + try { + document = await workspace.openTextDocument(uri); + } catch (error) { + await commands.executeCommand('vscode.open', uri, opts); + continue; + } // Check if active text editor has same path as other editor. we cannot compare via // URI.toString() here because the schemas can be different. Instead we just go by path. @@ -1225,23 +1240,35 @@ export class CommandCenter { opts?: CommitOptions ): Promise { const config = workspace.getConfiguration('git', Uri.file(repository.root)); - const promptToSaveFilesBeforeCommit = config.get('promptToSaveFilesBeforeCommit') === true; + let promptToSaveFilesBeforeCommit = config.get<'always' | 'staged' | 'never'>('promptToSaveFilesBeforeCommit'); - if (promptToSaveFilesBeforeCommit) { - const unsavedTextDocuments = workspace.textDocuments + // migration + if (promptToSaveFilesBeforeCommit as any === true) { + promptToSaveFilesBeforeCommit = 'always'; + } else if (promptToSaveFilesBeforeCommit as any === false) { + promptToSaveFilesBeforeCommit = 'never'; + } + + if (promptToSaveFilesBeforeCommit !== 'never') { + let documents = workspace.textDocuments .filter(d => !d.isUntitled && d.isDirty && isDescendant(repository.root, d.uri.fsPath)); - if (unsavedTextDocuments.length > 0) { - const message = unsavedTextDocuments.length === 1 - ? localize('unsaved files single', "The following file is unsaved: {0}.\n\nWould you like to save it before committing?", path.basename(unsavedTextDocuments[0].uri.fsPath)) - : localize('unsaved files', "There are {0} unsaved files.\n\nWould you like to save them before committing?", unsavedTextDocuments.length); + if (promptToSaveFilesBeforeCommit === 'staged') { + documents = documents + .filter(d => repository.indexGroup.resourceStates.some(s => s.resourceUri.path === d.uri.fsPath)); + } + + if (documents.length > 0) { + const message = documents.length === 1 + ? localize('unsaved files single', "The following file is unsaved and will not be included in the commit if you proceed: {0}.\n\nWould you like to save it before committing?", path.basename(documents[0].uri.fsPath)) + : localize('unsaved files', "There are {0} unsaved files.\n\nWould you like to save them before committing?", documents.length); const saveAndCommit = localize('save and commit', "Save All & Commit"); const commit = localize('commit', "Commit Anyway"); const pick = await window.showWarningMessage(message, { modal: true }, saveAndCommit, commit); if (pick === saveAndCommit) { - await Promise.all(unsavedTextDocuments.map(d => d.save())); - await repository.status(); + await Promise.all(documents.map(d => d.save())); + await repository.add(documents.map(d => d.uri)); } else if (pick !== commit) { return false; // do not commit on cancel } @@ -1255,15 +1282,24 @@ export class CommandCenter { // no changes, and the user has not configured to commit all in this case if (!noUnstagedChanges && noStagedChanges && !enableSmartCommit) { + const suggestSmartCommit = config.get('suggestSmartCommit') === true; + + if (!suggestSmartCommit) { + return false; + } // prompt the user if we want to commit all or not const message = localize('no staged changes', "There are no staged changes to commit.\n\nWould you like to automatically stage all your changes and commit them directly?"); const yes = localize('yes', "Yes"); const always = localize('always', "Always"); - const pick = await window.showWarningMessage(message, { modal: true }, yes, always); + const never = localize('never', "Never"); + const pick = await window.showWarningMessage(message, { modal: true }, yes, always, never); if (pick === always) { config.update('enableSmartCommit', true, true); + } else if (pick === never) { + config.update('suggestSmartCommit', false, true); + return false; } else if (pick !== yes) { return false; // do not commit on cancel } @@ -1301,6 +1337,10 @@ export class CommandCenter { return false; } + if (opts.all && config.get<'all' | 'tracked'>('smartCommitChanges') === 'tracked') { + opts.all = 'tracked'; + } + await repository.commit(message, opts); const postCommitCommand = config.get<'none' | 'push' | 'sync'>('postCommitCommand'); @@ -1350,19 +1390,6 @@ export class CommandCenter { await this.commitWithAnyInput(repository); } - @command('git.commitWithInput', { repository: true }) - async commitWithInput(repository: Repository): Promise { - if (!repository.inputBox.value) { - return; - } - - const didCommit = await this.smartCommit(repository, async () => repository.inputBox.value); - - if (didCommit) { - repository.inputBox.value = await repository.getCommitTemplate(); - } - } - @command('git.commitStaged', { repository: true }) async commitStaged(repository: Repository): Promise { await this.commitWithAnyInput(repository, { all: false }); @@ -1487,12 +1514,12 @@ export class CommandCenter { await this._branch(repository, undefined, true); } - private async _branch(repository: Repository, defaultName?: string, from = false): Promise { + private async promptForBranchName(defaultName?: string): Promise { const config = workspace.getConfiguration('git'); const branchWhitespaceChar = config.get('branchWhitespaceChar')!; const branchValidationRegex = config.get('branchValidationRegex')!; const sanitize = (name: string) => name ? - name.trim().replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, branchWhitespaceChar) + name.trim().replace(/^-+/, '').replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, branchWhitespaceChar) : name; const rawBranchName = defaultName || await window.showInputBox({ @@ -1509,7 +1536,11 @@ export class CommandCenter { } }); - const branchName = sanitize(rawBranchName || ''); + return sanitize(rawBranchName || ''); + } + + private async _branch(repository: Repository, defaultName?: string, from = false): Promise { + const branchName = await this.promptForBranchName(defaultName); if (!branchName) { return; @@ -1571,25 +1602,21 @@ export class CommandCenter { @command('git.renameBranch', { repository: true }) async renameBranch(repository: Repository): Promise { - const name = await window.showInputBox({ - placeHolder: localize('branch name', "Branch name"), - prompt: localize('provide branch name', "Please provide a branch name"), - value: repository.HEAD && repository.HEAD.name - }); + const branchName = await this.promptForBranchName(); - if (!name || name.trim().length === 0) { + if (!branchName) { return; } try { - await repository.renameBranch(name); + await repository.renameBranch(branchName); } catch (err) { switch (err.gitErrorCode) { case GitErrorCodes.InvalidBranchName: window.showErrorMessage(localize('invalid branch name', 'Invalid branch name')); return; case GitErrorCodes.BranchAlreadyExists: - window.showErrorMessage(localize('branch already exists', "A branch named '{0}' already exists", name)); + window.showErrorMessage(localize('branch already exists', "A branch named '{0}' already exists", branchName)); return; default: throw err; @@ -1913,7 +1940,17 @@ export class CommandCenter { private async _sync(repository: Repository, rebase: boolean): Promise { const HEAD = repository.HEAD; - if (!HEAD || !HEAD.upstream) { + if (!HEAD) { + return; + } else if (!HEAD.upstream) { + const branchName = HEAD.name; + const message = localize('confirm publish branch', "The branch '{0}' has no upstream branch. Would you like to publish this branch?", branchName); + const yes = localize('ok', "OK"); + const pick = await window.showWarningMessage(message, { modal: true }, yes); + + if (pick === yes) { + await this.publish(repository); + } return; } @@ -1945,8 +1982,16 @@ export class CommandCenter { } @command('git.sync', { repository: true }) - sync(repository: Repository): Promise { - return this._sync(repository, false); + async sync(repository: Repository): Promise { + try { + await this._sync(repository, false); + } catch (err) { + if (/Cancelled/i.test(err && (err.message || err.stderr || ''))) { + return; + } + + throw err; + } } @command('git._syncAll') @@ -1963,8 +2008,16 @@ export class CommandCenter { } @command('git.syncRebase', { repository: true }) - syncRebase(repository: Repository): Promise { - return this._sync(repository, true); + async syncRebase(repository: Repository): Promise { + try { + await this._sync(repository, true); + } catch (err) { + if (/Cancelled/i.test(err && (err.message || err.stderr || ''))) { + return; + } + + throw err; + } } @command('git.publish', { repository: true }) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 15f0f16062..fc3141d689 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -11,12 +11,15 @@ import * as which from 'which'; import { EventEmitter } from 'events'; import iconv = require('iconv-lite'); import * as filetype from 'file-type'; -import { assign, groupBy, denodeify, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent } from './util'; +import { assign, groupBy, denodeify, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter } from './util'; import { CancellationToken } from 'vscode'; import { URI } from 'vscode-uri'; import { detectEncoding } from './encoding'; import { Ref, RefType, Branch, Remote, GitErrorCodes, LogOptions, Change, Status } from './api/git'; +// https://github.com/microsoft/vscode/issues/65693 +const MAX_CLI_LENGTH = 30000; + const readfile = denodeify(fs.readFile); export interface IGit { @@ -339,7 +342,7 @@ export class Git { } async clone(url: string, parentPath: string, cancellationToken?: CancellationToken): Promise { - let baseFolderName = decodeURI(url).replace(/[\/]+$/, '').replace(/^.*\//, '').replace(/\.git$/, '') || 'repository'; + let baseFolderName = decodeURI(url).replace(/[\/]+$/, '').replace(/^.*[\/\\]/, '').replace(/\.git$/, '') || 'repository'; let folderName = baseFolderName; let folderPath = path.join(parentPath, folderName); let count = 1; @@ -598,13 +601,13 @@ export function parseGitmodules(raw: string): Submodule[] { } export function parseGitCommit(raw: string): Commit | null { - const match = /^([0-9a-f]{40})\n(.*)\n(.*)\n([^]*)$/m.exec(raw.trim()); + const match = /^([0-9a-f]{40})\n(.*)\n(.*)(\n([^]*))?$/m.exec(raw.trim()); if (!match) { return null; } const parents = match[3] ? match[3].split(' ') : []; - return { hash: match[1], message: match[4], parents, authorEmail: match[2] }; + return { hash: match[1], message: match[5], parents, authorEmail: match[2] }; } interface LsTreeElement { @@ -639,7 +642,7 @@ export function parseLsFiles(raw: string): LsFilesElement[] { } export interface CommitOptions { - all?: boolean; + all?: boolean | 'tracked'; amend?: boolean; signoff?: boolean; signCommit?: boolean; @@ -649,6 +652,7 @@ export interface CommitOptions { export interface PullOptions { unshallow?: boolean; tags?: boolean; + readonly cancellationToken?: CancellationToken; } export enum ForcePushMode { @@ -797,7 +801,7 @@ export class Repository { const elements = await this.lsfiles(path); if (elements.length === 0) { - throw new GitError({ message: 'Error running ls-files' }); + throw new GitError({ message: 'Path not known by git', gitErrorCode: GitErrorCodes.UnknownPath }); } const { mode, object } = elements[0]; @@ -810,7 +814,7 @@ export class Repository { const elements = await this.lstree(treeish, path); if (elements.length === 0) { - throw new GitError({ message: 'Error running ls-files' }); + throw new GitError({ message: 'Path not known by git', gitErrorCode: GitErrorCodes.UnknownPath }); } const { mode, object, size } = elements[0]; @@ -1077,8 +1081,16 @@ export class Repository { return result.stdout.trim(); } - async add(paths: string[]): Promise { - const args = ['add', '-A', '--']; + async add(paths: string[], opts?: { update?: boolean }): Promise { + const args = ['add']; + + if (opts && opts.update) { + args.push('-u'); + } else { + args.push('-A'); + } + + args.push('--'); if (paths && paths.length) { args.push.apply(args, paths); @@ -1116,15 +1128,21 @@ export class Repository { } let mode: string; + let add: string = ''; try { const details = await this.getObjectDetails('HEAD', path); mode = details.mode; } catch (err) { + if (err.gitErrorCode !== GitErrorCodes.UnknownPath) { + throw err; + } + mode = '100644'; + add = '--add'; } - await this.run(['update-index', '--cacheinfo', mode, hash, path]); + await this.run(['update-index', add, '--cacheinfo', mode, hash, path]); } async checkout(treeish: string, paths: string[], opts: { track?: boolean } = Object.create(null)): Promise { @@ -1138,13 +1156,14 @@ export class Repository { args.push(treeish); } - if (paths && paths.length) { - args.push('--'); - args.push.apply(args, paths); - } - try { - await this.run(args); + if (paths && paths.length > 0) { + for (const chunk of splitInChunks(paths, MAX_CLI_LENGTH)) { + await this.run([...args, '--', ...chunk]); + } + } else { + await this.run(args); + } } catch (err) { if (/Please,? commit your changes or stash them/.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.DirtyWorkTree; @@ -1275,11 +1294,17 @@ export class Repository { async clean(paths: string[]): Promise { const pathsByGroup = groupBy(paths, p => path.dirname(p)); const groups = Object.keys(pathsByGroup).map(k => pathsByGroup[k]); - const tasks = groups.map(paths => () => this.run(['clean', '-f', '-q', '--'].concat(paths))); - for (let task of tasks) { - await task(); + const limiter = new Limiter(5); + const promises: Promise[] = []; + + for (const paths of groups) { + for (const chunk of splitInChunks(paths, MAX_CLI_LENGTH)) { + promises.push(limiter.queue(() => this.run(['clean', '-f', '-q', '--', ...chunk]))); + } } + + await Promise.all(promises); } async undo(): Promise { @@ -1396,7 +1421,7 @@ export class Repository { } try { - await this.run(args); + await this.run(args, options); } catch (err) { if (/^CONFLICT \([^)]+\): \b/m.test(err.stdout || '')) { err.gitErrorCode = GitErrorCodes.Conflict; @@ -1669,13 +1694,16 @@ export class Repository { async getBranch(name: string): Promise { if (name === 'HEAD') { return this.getHEAD(); - } else if (/^@/.test(name)) { - const symbolicFullNameResult = await this.run(['rev-parse', '--symbolic-full-name', name]); - const symbolicFullName = symbolicFullNameResult.stdout.trim(); - name = symbolicFullName || name; } - const result = await this.run(['rev-parse', name]); + let result = await this.run(['rev-parse', name]); + + if (!result.stdout && /^@/.test(name)) { + const symbolicFullNameResult = await this.run(['rev-parse', '--symbolic-full-name', name]); + name = symbolicFullNameResult.stdout.trim(); + + result = await this.run(['rev-parse', name]); + } if (!result.stdout) { return Promise.reject(new Error('No such branch')); @@ -1732,7 +1760,7 @@ export class Repository { } const raw = await readfile(templatePath, 'utf8'); - return raw.replace(/^\s*#.*$\n?/gm, '').trim(); + return raw.replace(/\n?#.*/g, ''); } catch (err) { return ''; @@ -1745,8 +1773,11 @@ export class Repository { } async updateSubmodules(paths: string[]): Promise { - const args = ['submodule', 'update', '--', ...paths]; - await this.run(args); + const args = ['submodule', 'update', '--']; + + for (const chunk of splitInChunks(paths, MAX_CLI_LENGTH)) { + await this.run([...args, ...chunk]); + } } async getSubmodules(): Promise { diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index c2a86a45ed..517258f74f 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -3,9 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { commands, Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, SourceControlInputBoxValidation, Disposable, ProgressLocation, window, workspace, WorkspaceEdit, ThemeColor, DecorationData, Memento, SourceControlInputBoxValidationType, OutputChannel, LogLevel, env } from 'vscode'; +import { commands, Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, SourceControlInputBoxValidation, Disposable, ProgressLocation, window, workspace, WorkspaceEdit, ThemeColor, DecorationData, Memento, SourceControlInputBoxValidationType, OutputChannel, LogLevel, env, ProgressOptions, CancellationToken } from 'vscode'; import { Repository as BaseRepository, Commit, Stash, GitError, Submodule, CommitOptions, ForcePushMode } from './git'; -import { anyEvent, filterEvent, eventToPromise, dispose, find, isDescendant, IDisposable, onceEvent, EmptyDisposable, debounceEvent, combinedDisposable, watch, IFileWatcher } from './util'; +import { anyEvent, filterEvent, eventToPromise, dispose, find, isDescendant, IDisposable, onceEvent, EmptyDisposable, debounceEvent, combinedDisposable } from './util'; import { memoize, throttle, debounce } from './decorators'; import { toGitUri } from './uri'; import { AutoFetcher } from './autofetch'; @@ -14,6 +14,7 @@ import * as nls from 'vscode-nls'; import * as fs from 'fs'; import { StatusBarCommands } from './statusbar'; import { Branch, Ref, Remote, RefType, GitErrorCodes, Status, LogOptions, Change } from './api/git'; +import { IFileWatcher, watch } from './watch'; const timeout = (millis: number) => new Promise(c => setTimeout(c, millis)); @@ -678,12 +679,15 @@ export class Repository implements Disposable { const root = Uri.file(repository.root); this._sourceControl = scm.createSourceControl('git', 'Git', root); - this._sourceControl.inputBox.placeholder = localize('commitMessage', "Message (press {0} to commit)"); - this._sourceControl.acceptInputCommand = { command: 'git.commitWithInput', title: localize('commit', "Commit"), arguments: [this._sourceControl] }; + + this._sourceControl.acceptInputCommand = { command: 'git.commit', title: localize('commit', "Commit"), arguments: [this._sourceControl] }; this._sourceControl.quickDiffProvider = this; this._sourceControl.inputBox.validateInput = this.validateInput.bind(this); this.disposables.push(this._sourceControl); + this.updateInputBoxPlaceholder(); + this.disposables.push(this.onDidRunGitStatus(() => this.updateInputBoxPlaceholder())); + this._mergeGroup = this._sourceControl.createResourceGroup('merge', localize('merge changes', "MERGE CHANGES")); this._indexGroup = this._sourceControl.createResourceGroup('index', localize('staged changes', "STAGED CHANGES")); this._workingTreeGroup = this._sourceControl.createResourceGroup('workingTree', localize('changes', "CHANGES")); @@ -723,6 +727,10 @@ export class Repository implements Disposable { const progressManager = new ProgressManager(this); this.disposables.push(progressManager); + const onDidChangeCountBadge = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.countBadge', root)); + onDidChangeCountBadge(this.setCountBadge, this, this.disposables); + this.setCountBadge(); + this.updateCommitTemplate(); } @@ -905,7 +913,8 @@ export class Repository implements Disposable { if (this.rebaseCommit) { await this.run(Operation.RebaseContinue, async () => { if (opts.all) { - await this.repository.add([]); + const addOpts = opts.all === 'tracked' ? { update: true } : {}; + await this.repository.add([], addOpts); } await this.repository.rebaseContinue(); @@ -913,9 +922,11 @@ export class Repository implements Disposable { } else { await this.run(Operation.Commit, async () => { if (opts.all) { - await this.repository.add([]); + const addOpts = opts.all === 'tracked' ? { update: true } : {}; + await this.repository.add([], addOpts); } + delete opts.all; await this.repository.commit(message, opts); }); } @@ -956,21 +967,9 @@ export class Repository implements Disposable { } }); - const promises: Promise[] = []; - - if (toClean.length > 0) { - promises.push(this.repository.clean(toClean)); - } - - if (toCheckout.length > 0) { - promises.push(this.repository.checkout('', toCheckout)); - } - - if (submodulesToUpdate.length > 0) { - promises.push(this.repository.updateSubmodules(submodulesToUpdate)); - } - - await Promise.all(promises); + await this.repository.clean(toClean); + await this.repository.checkout('', toCheckout); + await this.repository.updateSubmodules(submodulesToUpdate); }); } @@ -1146,11 +1145,22 @@ export class Repository implements Disposable { const config = workspace.getConfiguration('git', Uri.file(this.root)); const fetchOnPull = config.get('fetchOnPull'); const tags = config.get('pullTags'); + const supportCancellation = config.get('supportCancellation'); - if (fetchOnPull) { - await this.repository.pull(rebase, undefined, undefined, { tags }); + const fn = fetchOnPull + ? async (cancellationToken?: CancellationToken) => await this.repository.pull(rebase, undefined, undefined, { tags, cancellationToken }) + : async (cancellationToken?: CancellationToken) => await this.repository.pull(rebase, remoteName, pullBranch, { tags, cancellationToken }); + + if (supportCancellation) { + const opts: ProgressOptions = { + location: ProgressLocation.Notification, + title: localize('sync is unpredictable', "Syncing. Cancelling may cause serious damages to the repository"), + cancellable: true + }; + + await window.withProgress(opts, (_, token) => fn(token)); } else { - await this.repository.pull(rebase, remoteName, pullBranch, { tags }); + await fn(); } const remote = this.remotes.find(r => r.name === remoteName); @@ -1495,15 +1505,7 @@ export class Repository implements Disposable { this.workingTreeGroup.resourceStates = workingTree; // set count badge - const countBadge = workspace.getConfiguration('git').get('countBadge'); - let count = merge.length + index.length + workingTree.length; - - switch (countBadge) { - case 'off': count = 0; break; - case 'tracked': count = count - workingTree.filter(r => r.type === Status.UNTRACKED || r.type === Status.IGNORED).length; break; - } - - this._sourceControl.count = count; + this.setCountBadge(); // Disable `Discard All Changes` for "fresh" repositories // https://github.com/Microsoft/vscode/issues/43066 @@ -1517,6 +1519,18 @@ export class Repository implements Disposable { this._onDidChangeStatus.fire(); } + private setCountBadge(): void { + const countBadge = workspace.getConfiguration('git').get('countBadge'); + let count = this.mergeGroup.resourceStates.length + this.indexGroup.resourceStates.length + this.workingTreeGroup.resourceStates.length; + + switch (countBadge) { + case 'off': count = 0; break; + case 'tracked': count = count - this.workingTreeGroup.resourceStates.filter(r => r.type === Status.UNTRACKED || r.type === Status.IGNORED).length; break; + } + + this._sourceControl.count = count; + } + private async getRebaseCommit(): Promise { const rebaseHeadPath = path.join(this.repository.root, '.git', 'REBASE_HEAD'); const rebaseApplyPath = path.join(this.repository.root, '.git', 'rebase-apply'); @@ -1638,6 +1652,21 @@ export class Repository implements Disposable { return `${this.HEAD.behind}↓ ${this.HEAD.ahead}↑`; } + private updateInputBoxPlaceholder(): void { + const HEAD = this.HEAD; + + if (HEAD) { + const tag = this.refs.filter(iref => iref.type === RefType.Tag && iref.commit === HEAD.commit)[0]; + const tagName = tag && tag.name; + const head = HEAD.name || tagName || (HEAD.commit || '').substr(0, 8); + + // '{0}' will be replaced by the corresponding key-command later in the process, which is why it needs to stay. + this._sourceControl.inputBox.placeholder = localize('commitMessageWithHeadLabel', "Message ({0} to commit on '{1}')", "{0}", head); + } else { + this._sourceControl.inputBox.placeholder = localize('commitMessage', "Message ({0} to commit)"); + } + } + dispose(): void { this.disposables = dispose(this.disposables); } diff --git a/extensions/git/src/statusbar.ts b/extensions/git/src/statusbar.ts index 433e6bd2b1..67c5bf0c72 100644 --- a/extensions/git/src/statusbar.ts +++ b/extensions/git/src/statusbar.ts @@ -5,7 +5,7 @@ import { Disposable, Command, EventEmitter, Event, workspace, Uri } from 'vscode'; import { Repository, Operation } from './repository'; -import { anyEvent, dispose } from './util'; +import { anyEvent, dispose, filterEvent } from './util'; import * as nls from 'vscode-nls'; import { Branch } from './api/git'; @@ -27,7 +27,7 @@ class CheckoutStatusBar { return { command: 'git.checkout', - tooltip: localize('checkout', 'Checkout...'), + tooltip: `${this.repository.headLabel}`, title, arguments: [this.repository.sourceControl] }; @@ -39,6 +39,7 @@ class CheckoutStatusBar { } interface SyncStatusBarState { + enabled: boolean; isSyncRunning: boolean; hasRemotes: boolean; HEAD: Branch | undefined; @@ -47,6 +48,7 @@ interface SyncStatusBarState { class SyncStatusBar { private static StartState: SyncStatusBarState = { + enabled: true, isSyncRunning: false, hasRemotes: false, HEAD: undefined @@ -66,9 +68,20 @@ class SyncStatusBar { constructor(private repository: Repository) { repository.onDidRunGitStatus(this.onModelChange, this, this.disposables); repository.onDidChangeOperations(this.onOperationsChange, this, this.disposables); + + const onEnablementChange = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.enableStatusBarSync')); + onEnablementChange(this.updateEnablement, this, this.disposables); + this._onDidChange.fire(); } + private updateEnablement(): void { + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const enabled = config.get('enableStatusBarSync', true); + + this.state = { ... this.state, enabled }; + } + private onOperationsChange(): void { const isSyncRunning = this.repository.operations.isRunning(Operation.Sync) || this.repository.operations.isRunning(Operation.Push) || @@ -86,7 +99,7 @@ class SyncStatusBar { } get command(): Command | undefined { - if (!this.state.hasRemotes) { + if (!this.state.enabled || !this.state.hasRemotes) { return undefined; } diff --git a/extensions/git/src/test/git.test.ts b/extensions/git/src/test/git.test.ts index facabe3550..54e6aaab5c 100644 --- a/extensions/git/src/test/git.test.ts +++ b/extensions/git/src/test/git.test.ts @@ -6,6 +6,7 @@ import 'mocha'; import { GitStatusParser, parseGitCommit, parseGitmodules, parseLsTree, parseLsFiles } from '../git'; import * as assert from 'assert'; +import { splitInChunks } from '../util'; suite('git', () => { suite('GitStatusParser', () => { @@ -292,4 +293,78 @@ This is a commit message.`; ]); }); }); -}); \ No newline at end of file + + suite('splitInChunks', () => { + test('unit tests', function () { + assert.deepEqual( + [...splitInChunks(['hello', 'there', 'cool', 'stuff'], 6)], + [['hello'], ['there'], ['cool'], ['stuff']] + ); + + assert.deepEqual( + [...splitInChunks(['hello', 'there', 'cool', 'stuff'], 10)], + [['hello', 'there'], ['cool', 'stuff']] + ); + + assert.deepEqual( + [...splitInChunks(['hello', 'there', 'cool', 'stuff'], 12)], + [['hello', 'there'], ['cool', 'stuff']] + ); + + assert.deepEqual( + [...splitInChunks(['hello', 'there', 'cool', 'stuff'], 14)], + [['hello', 'there', 'cool'], ['stuff']] + ); + + assert.deepEqual( + [...splitInChunks(['hello', 'there', 'cool', 'stuff'], 2000)], + [['hello', 'there', 'cool', 'stuff']] + ); + + assert.deepEqual( + [...splitInChunks(['0', '01', '012', '0', '01', '012', '0', '01', '012'], 1)], + [['0'], ['01'], ['012'], ['0'], ['01'], ['012'], ['0'], ['01'], ['012']] + ); + + assert.deepEqual( + [...splitInChunks(['0', '01', '012', '0', '01', '012', '0', '01', '012'], 2)], + [['0'], ['01'], ['012'], ['0'], ['01'], ['012'], ['0'], ['01'], ['012']] + ); + + assert.deepEqual( + [...splitInChunks(['0', '01', '012', '0', '01', '012', '0', '01', '012'], 3)], + [['0', '01'], ['012'], ['0', '01'], ['012'], ['0', '01'], ['012']] + ); + + assert.deepEqual( + [...splitInChunks(['0', '01', '012', '0', '01', '012', '0', '01', '012'], 4)], + [['0', '01'], ['012', '0'], ['01'], ['012', '0'], ['01'], ['012']] + ); + + assert.deepEqual( + [...splitInChunks(['0', '01', '012', '0', '01', '012', '0', '01', '012'], 5)], + [['0', '01'], ['012', '0'], ['01', '012'], ['0', '01'], ['012']] + ); + + assert.deepEqual( + [...splitInChunks(['0', '01', '012', '0', '01', '012', '0', '01', '012'], 6)], + [['0', '01', '012'], ['0', '01', '012'], ['0', '01', '012']] + ); + + assert.deepEqual( + [...splitInChunks(['0', '01', '012', '0', '01', '012', '0', '01', '012'], 7)], + [['0', '01', '012', '0'], ['01', '012', '0'], ['01', '012']] + ); + + assert.deepEqual( + [...splitInChunks(['0', '01', '012', '0', '01', '012', '0', '01', '012'], 8)], + [['0', '01', '012', '0'], ['01', '012', '0', '01'], ['012']] + ); + + assert.deepEqual( + [...splitInChunks(['0', '01', '012', '0', '01', '012', '0', '01', '012'], 9)], + [['0', '01', '012', '0', '01'], ['012', '0', '01', '012']] + ); + }); + }); +}); diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index 7dc9927a3c..1fc8553767 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -3,8 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event, EventEmitter, Uri } from 'vscode'; -import { dirname, sep, join } from 'path'; +import { Event } from 'vscode'; +import { dirname, sep } from 'path'; import { Readable } from 'stream'; import * as fs from 'fs'; import * as byline from 'byline'; @@ -345,18 +345,69 @@ export function pathEquals(a: string, b: string): boolean { return a === b; } -export interface IFileWatcher extends IDisposable { - readonly event: Event; +export function* splitInChunks(array: string[], maxChunkLength: number): IterableIterator { + let current: string[] = []; + let length = 0; + + for (const value of array) { + let newLength = length + value.length; + + if (newLength > maxChunkLength && current.length > 0) { + yield current; + current = []; + newLength = value.length; + } + + current.push(value); + length = newLength; + } + + if (current.length > 0) { + yield current; + } } -export function watch(location: string): IFileWatcher { - const dotGitWatcher = fs.watch(location); - const onDotGitFileChangeEmitter = new EventEmitter(); - dotGitWatcher.on('change', (_, e) => onDotGitFileChangeEmitter.fire(Uri.file(join(location, e as string)))); - dotGitWatcher.on('error', err => console.error(err)); - - return new class implements IFileWatcher { - event = onDotGitFileChangeEmitter.event; - dispose() { dotGitWatcher.close(); } - }; +interface ILimitedTaskFactory { + factory: () => Promise; + c: (value?: T | Promise) => void; + e: (error?: any) => void; +} + +export class Limiter { + + private runningPromises: number; + private maxDegreeOfParalellism: number; + private outstandingPromises: ILimitedTaskFactory[]; + + constructor(maxDegreeOfParalellism: number) { + this.maxDegreeOfParalellism = maxDegreeOfParalellism; + this.outstandingPromises = []; + this.runningPromises = 0; + } + + queue(factory: () => Promise): Promise { + return new Promise((c, e) => { + this.outstandingPromises.push({ factory, c, e }); + this.consume(); + }); + } + + private consume(): void { + while (this.outstandingPromises.length && this.runningPromises < this.maxDegreeOfParalellism) { + const iLimitedTask = this.outstandingPromises.shift()!; + this.runningPromises++; + + const promise = iLimitedTask.factory(); + promise.then(iLimitedTask.c, iLimitedTask.e); + promise.then(() => this.consumed(), () => this.consumed()); + } + } + + private consumed(): void { + this.runningPromises--; + + if (this.outstandingPromises.length > 0) { + this.consume(); + } + } } diff --git a/extensions/git/src/watch.ts b/extensions/git/src/watch.ts new file mode 100644 index 0000000000..de995a500f --- /dev/null +++ b/extensions/git/src/watch.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event, EventEmitter, Uri } from 'vscode'; +import { join } from 'path'; +import * as fs from 'fs'; +import { IDisposable } from './util'; + +export interface IFileWatcher extends IDisposable { + readonly event: Event; +} + +export function watch(location: string): IFileWatcher { + const dotGitWatcher = fs.watch(location); + const onDotGitFileChangeEmitter = new EventEmitter(); + dotGitWatcher.on('change', (_, e) => onDotGitFileChangeEmitter.fire(Uri.file(join(location, e as string)))); + dotGitWatcher.on('error', err => console.error(err)); + + return new class implements IFileWatcher { + event = onDotGitFileChangeEmitter.event; + dispose() { dotGitWatcher.close(); } + }; +} diff --git a/extensions/json-language-features/client/src/jsonMain.ts b/extensions/json-language-features/client/src/jsonMain.ts index a4821627cb..345263ef17 100644 --- a/extensions/json-language-features/client/src/jsonMain.ts +++ b/extensions/json-language-features/client/src/jsonMain.ts @@ -223,7 +223,7 @@ export function activate(context: ExtensionContext) { let languageConfiguration: LanguageConfiguration = { wordPattern: /("(?:[^\\\"]*(?:\\.)?)*"?)|[^\s{}\[\],:]+/, indentationRules: { - increaseIndentPattern: /^.*(\{[^}]*|\[[^\]]*)$/, + increaseIndentPattern: /({+(?=([^"]*"[^"]*")*[^"}]*$))|(\[+(?=([^"]*"[^"]*")*[^"\]]*$))/, decreaseIndentPattern: /^\s*[}\]],?\s*$/ } }; diff --git a/extensions/json/package.json b/extensions/json/package.json index 1606f6a3d6..e23e86b14a 100644 --- a/extensions/json/package.json +++ b/extensions/json/package.json @@ -50,7 +50,8 @@ ".babelrc", ".jsonc", ".eslintrc", - ".eslintrc.json" + ".eslintrc.json", + "tslint.json" ], "configuration": "./language-configuration.json" } @@ -74,4 +75,4 @@ } ] } -} \ No newline at end of file +} diff --git a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json index b94e9eba4a..f63c46a878 100644 --- a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json +++ b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json @@ -2566,4 +2566,4 @@ "name": "markup.inline.raw.string.markdown" } } -} \ No newline at end of file +} diff --git a/extensions/package.json b/extensions/package.json index 52ece5e205..57d5f5da31 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "3.5.2" + "typescript": "3.6.0-dev.20190810" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/powershell/cgmanifest.json b/extensions/powershell/cgmanifest.json index e9adf03ca0..c8dcfd7cc8 100644 --- a/extensions/powershell/cgmanifest.json +++ b/extensions/powershell/cgmanifest.json @@ -5,13 +5,13 @@ "type": "git", "git": { "name": "PowerShell/EditorSyntax", - "repositoryUrl": "https://github.com/powershell/editorsyntax", - "commitHash": "12b7d7257eb493e45a9af0af9094ec0c2a996712" + "repositoryUrl": "https://github.com/PowerShell/EditorSyntax", + "commitHash": "d10ae29c0d3ceb248172c383a159ae43b9ccfb4d" } }, "license": "MIT", - "version": "0.0.0" + "version": "1.0.0" } ], "version": 1 -} \ No newline at end of file +} diff --git a/extensions/powershell/syntaxes/powershell.tmLanguage.json b/extensions/powershell/syntaxes/powershell.tmLanguage.json index 59ee6fbd13..de473e19fb 100644 --- a/extensions/powershell/syntaxes/powershell.tmLanguage.json +++ b/extensions/powershell/syntaxes/powershell.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/PowerShell/EditorSyntax/commit/44eac8702f3cbe55a4ec70c1fdb163d42b4162fc", + "version": "https://github.com/PowerShell/EditorSyntax/commit/d10ae29c0d3ceb248172c383a159ae43b9ccfb4d", "name": "PowerShell", "scopeName": "source.powershell", "patterns": [ @@ -670,7 +670,7 @@ } }, "comment": "Style preference variables as language variables so that they stand out.", - "match": "(\\$)(?i:(ConfirmPreference|DebugPreference|ErrorActionPreference|ErrorView|FormatEnumerationLimit|MaximumAliasCount|MaximumDriveCount|MaximumErrorCount|MaximumFunctionCount|MaximumHistoryCount|MaximumVariableCount|OFS|OutputEncoding|ProgressPreference|PsCulture|PSDebugContext|PSDefaultParameterValues|PSEmailServer|PSItem|PSModuleAutoloadingPreference|PSSenderInfo|PSSessionApplicationName|PSSessionConfigurationName|PSSessionOption|VerbosePreference|WarningPreference|WhatIfPreference))((?:\\.(?:\\p{L}|\\d|_)+)*\\b)?\\b" + "match": "(\\$)(?i:(ConfirmPreference|DebugPreference|ErrorActionPreference|ErrorView|FormatEnumerationLimit|InformationPreference|LogCommandHealthEvent|LogCommandLifecycleEvent|LogEngineHealthEvent|LogEngineLifecycleEvent|LogProviderHealthEvent|LogProviderLifecycleEvent|MaximumAliasCount|MaximumDriveCount|MaximumErrorCount|MaximumFunctionCount|MaximumHistoryCount|MaximumVariableCount|OFS|OutputEncoding|PSCulture|PSDebugContext|PSDefaultParameterValues|PSEmailServer|PSItem|PSModuleAutoLoadingPreference|PSModuleAutoloadingPreference|PSSenderInfo|PSSessionApplicationName|PSSessionConfigurationName|PSSessionOption|ProgressPreference|VerbosePreference|WarningPreference|WhatIfPreference))((?:\\.(?:\\p{L}|\\d|_)+)*\\b)?\\b" }, { "captures": { @@ -848,7 +848,7 @@ } }, "comment": "Style preference variables as language variables so that they stand out.", - "match": "(\\$)(?i:(ConfirmPreference|DebugPreference|ErrorActionPreference|ErrorView|FormatEnumerationLimit|MaximumAliasCount|MaximumDriveCount|MaximumErrorCount|MaximumFunctionCount|MaximumHistoryCount|MaximumVariableCount|OFS|OutputEncoding|ProgressPreference|PsCulture|PSDebugContext|PSDefaultParameterValues|PSEmailServer|PSItem|PSModuleAutoloadingPreference|PSSenderInfo|PSSessionApplicationName|PSSessionConfigurationName|PSSessionOption|VerbosePreference|WarningPreference|WhatIfPreference))\\b" + "match": "(\\$)(?i:(ConfirmPreference|DebugPreference|ErrorActionPreference|ErrorView|FormatEnumerationLimit|InformationPreference|LogCommandHealthEvent|LogCommandLifecycleEvent|LogEngineHealthEvent|LogEngineLifecycleEvent|LogProviderHealthEvent|LogProviderLifecycleEvent|MaximumAliasCount|MaximumDriveCount|MaximumErrorCount|MaximumFunctionCount|MaximumHistoryCount|MaximumVariableCount|OFS|OutputEncoding|PSCulture|PSDebugContext|PSDefaultParameterValues|PSEmailServer|PSItem|PSModuleAutoLoadingPreference|PSModuleAutoloadingPreference|PSSenderInfo|PSSessionApplicationName|PSSessionConfigurationName|PSSessionOption|ProgressPreference|VerbosePreference|WarningPreference|WhatIfPreference))\\b" }, { "captures": { diff --git a/extensions/powershell/test/colorize-results/test_ps1.json b/extensions/powershell/test/colorize-results/test_ps1.json index e41d130649..67380ce77b 100644 --- a/extensions/powershell/test/colorize-results/test_ps1.json +++ b/extensions/powershell/test/colorize-results/test_ps1.json @@ -465,11 +465,11 @@ "c": ".IsInRole", "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell variable.other.readwrite.powershell variable.other.member.powershell", "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", + "dark_plus": "source.powershell variable.other.member: #DCDCAA", + "light_plus": "source.powershell variable.other.member: #795E26", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" + "hc_black": "source.powershell variable.other.member: #DCDCAA" } }, { diff --git a/extensions/theme-defaults/themes/dark_plus.json b/extensions/theme-defaults/themes/dark_plus.json index 1898153ba8..f12d3d7633 100644 --- a/extensions/theme-defaults/themes/dark_plus.json +++ b/extensions/theme-defaults/themes/dark_plus.json @@ -8,7 +8,8 @@ "scope": [ "entity.name.function", "support.function", - "support.constant.handlebars" + "support.constant.handlebars", + "source.powershell variable.other.member" ], "settings": { "foreground": "#DCDCAA" @@ -171,4 +172,4 @@ } } ] -} \ No newline at end of file +} diff --git a/extensions/theme-defaults/themes/hc_black.json b/extensions/theme-defaults/themes/hc_black.json index 8119256d5f..f76d7bb960 100644 --- a/extensions/theme-defaults/themes/hc_black.json +++ b/extensions/theme-defaults/themes/hc_black.json @@ -13,7 +13,8 @@ "scope": [ "entity.name.function", "support.function", - "support.constant.handlebars" + "support.constant.handlebars", + "source.powershell variable.other.member" ], "settings": { "foreground": "#DCDCAA" @@ -115,4 +116,4 @@ } } ] -} \ No newline at end of file +} diff --git a/extensions/theme-defaults/themes/light_plus.json b/extensions/theme-defaults/themes/light_plus.json index 7138f045d5..3d30c5d17f 100644 --- a/extensions/theme-defaults/themes/light_plus.json +++ b/extensions/theme-defaults/themes/light_plus.json @@ -8,7 +8,8 @@ "scope": [ "entity.name.function", "support.function", - "support.constant.handlebars" + "support.constant.handlebars", + "source.powershell variable.other.member" ], "settings": { "foreground": "#795E26" @@ -172,4 +173,4 @@ } ] -} \ No newline at end of file +} diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 8e552194c1..233cf189e1 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -typescript@3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.2.tgz#a09e1dc69bc9551cadf17dba10ee42cf55e5d56c" - integrity sha512-7KxJovlYhTX5RaRbUdkAXN1KUZ8PwWlTzQdHV6xNqvuFOs7+WBo10TQUqT19Q/Jz2hk5v9TQDIhyLhhJY4p5AA== +typescript@3.6.0-dev.20190810: + version "3.6.0-dev.20190810" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.0-dev.20190810.tgz#dda80279480131eec9b05e3b78182a1ba1efe105" + integrity sha512-gubcQ8Sn2G5AO1KhjvLpoFrutV7o/ZJ7wCDBC1IKgNI8R2vadIxTystJxAFqkb9boQ7tyRrZ6FwM5EL5ZYfJrg== diff --git a/package.json b/package.json index f6e8f2bb2a..1713ab7423 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@angular/platform-browser-dynamic": "~4.1.3", "@angular/router": "~4.1.3", "@angular/upgrade": "~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", @@ -53,8 +54,7 @@ "jquery": "3.4.0", "jschardet": "1.6.0", "keytar": "^4.11.0", - "minimist": "1.2.0", - "native-is-elevated": "^0.2.1", + "native-is-elevated": "0.3.0", "native-keymap": "2.0.0", "native-watchdog": "1.0.0", "ng2-charts": "^1.6.0", @@ -72,11 +72,12 @@ "underscore": "^1.8.2", "v8-inspect-profiler": "^0.0.20", "vscode-chokidar": "2.1.7", + "vscode-minimist": "^1.2.1", "vscode-proxy-agent": "0.4.0", - "vscode-ripgrep": "^1.5.5", + "vscode-ripgrep": "^1.5.6", "vscode-sqlite3": "4.0.8", "vscode-textmate": "^4.2.2", - "xterm": "3.15.0-beta90", + "xterm": "3.15.0-beta98", "xterm-addon-search": "0.2.0-beta3", "xterm-addon-web-links": "0.1.0-beta10", "yauzl": "^2.9.2", @@ -88,7 +89,6 @@ "@types/chart.js": "^2.7.31", "@types/htmlparser2": "^3.7.31", "@types/keytar": "^4.4.0", - "@types/minimist": "^1.2.0", "@types/mocha": "2.2.39", "@types/node": "^10.12.12", "@types/sanitize-html": "^1.18.2", @@ -187,7 +187,7 @@ }, "optionalDependencies": { "vscode-windows-ca-certs": "0.1.0", - "vscode-windows-registry": "1.0.1", + "vscode-windows-registry": "1.0.2", "windows-foreground-love": "0.2.0", "windows-mutex": "0.3.0", "windows-process-tree": "0.2.4" diff --git a/remote/package.json b/remote/package.json index 5f851213c9..d3ad3afaed 100644 --- a/remote/package.json +++ b/remote/package.json @@ -2,6 +2,7 @@ "name": "vscode-reh", "version": "0.0.0", "dependencies": { + "@microsoft/applicationinsights-web": "^2.1.1", "applicationinsights": "1.0.8", "getmac": "1.4.1", "graceful-fs": "4.1.11", @@ -9,7 +10,6 @@ "https-proxy-agent": "^2.2.1", "iconv-lite": "0.5.0", "jschardet": "1.6.0", - "minimist": "1.2.0", "native-watchdog": "1.0.0", "node-pty": "0.9.0-beta19", "nsfw": "1.2.5", @@ -17,10 +17,11 @@ "semver-umd": "^5.5.3", "spdlog": "^0.9.0", "vscode-chokidar": "2.1.7", + "vscode-minimist": "^1.2.1", "vscode-proxy-agent": "0.4.0", - "vscode-ripgrep": "^1.5.5", + "vscode-ripgrep": "^1.5.6", "vscode-textmate": "^4.2.2", - "xterm": "3.15.0-beta90", + "xterm": "3.15.0-beta98", "xterm-addon-search": "0.2.0-beta3", "xterm-addon-web-links": "0.1.0-beta10", "yauzl": "^2.9.2", diff --git a/remote/web/package.json b/remote/web/package.json index c7a487b6c2..f54afe1a3e 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -2,6 +2,7 @@ "name": "vscode-web", "version": "0.0.0", "dependencies": { + "@microsoft/applicationinsights-web": "^2.1.1", "onigasm-umd": "^2.2.2", "vscode-textmate": "^4.1.1", "xterm": "3.15.0-beta67", diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index b624eb3729..23ad784a55 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -2,6 +2,69 @@ # yarn lockfile v1 +"@microsoft/applicationinsights-analytics-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-2.1.1.tgz#6d09c1915f808026e2d45165d04802f09affed59" + integrity sha512-VKIutoFKY99CyKwxLUuj6Vnq14/QwXo9/QSQDpYnHEjo+uKn7QmLsHqWw0K9uYNfNAXt4BZimX/zDg6jZtzeXg== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-channel-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.1.1.tgz#e205eddd93e49d17d9e0711a612b4bfc9810888f" + integrity sha512-fYr9IAqtaEr9AmaPaL3SLQVT3t3GQzl+n74gpNKyAVakDIm0nYQ/bimjdcAhJMDf1VGNSPg/xICneyuZg7Wxlg== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-common@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-common/-/applicationinsights-common-2.1.1.tgz#27e6074584a7a3a8ca3f11f7ff2b7ff0f395bf2d" + integrity sha512-2hkS1Ia1FmAjCuYZ5JlG20/WgObqdsKtmK5YALAFGHIB4KSQ/Za1qazS+7GsG+E0F9UJivNWL1geUIcNqg5Qjg== + dependencies: + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-core-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.1.1.tgz#30fb6a519cc1c6119c419c4811ce72c260217d9e" + integrity sha512-4t4wf6SKqIcWEQDPg/uOhm+BxtHhu/AFreyEoYZmMfcxzAu33h1FtTQRtxBNbYH1+thiNZCh80yUpnT7d9Hrlw== + dependencies: + tslib "^1.9.3" + +"@microsoft/applicationinsights-dependencies-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-2.1.1.tgz#8154c3efcb24617d015d0bce7c2cc47797a8d3c4" + integrity sha512-yhb4EToBp+aI+qLo0h5NDNtoo3sDFV60uyIOK843YjzXqVotcXX/lRShlghTkJtYH09QhrdzDjViUHnD4sMFSQ== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-properties-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-2.1.1.tgz#ca34232766eb16167b5d87693e2ae5d94f2a1559" + integrity sha512-8l+/ppw6xKTam2RL4EHZ52Lcf217olw81j6kyBNKtIcGwSnLNHrFwEeF3vBWIteG2JKzlg1GhGjrkB3oxXsV2g== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-web@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web/-/applicationinsights-web-2.1.1.tgz#1a44eddda7c244b88d9eb052dab6c855682e4f05" + integrity sha512-crvhCkNsNxkFuPWmttyWNSAA96D5FxBtKS6UA9MV9f9XHevTfchf/E3AuU9JZcsXufWMQLwLrUQ9ZiA1QJ0EWA== + dependencies: + "@microsoft/applicationinsights-analytics-js" "2.1.1" + "@microsoft/applicationinsights-channel-js" "2.1.1" + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + "@microsoft/applicationinsights-dependencies-js" "2.1.1" + "@microsoft/applicationinsights-properties-js" "2.1.1" + nan@^2.14.0: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" @@ -24,6 +87,11 @@ semver-umd@^5.5.3: resolved "https://registry.yarnpkg.com/semver-umd/-/semver-umd-5.5.3.tgz#b64d7a2d4f5a717b369d56e31940a38e47e34d1e" integrity sha512-HOnQrn2iKnVe/xlqCTzMXQdvSz3rPbD0DmQXYuQ+oK1dpptGFfPghonQrx5JHl2O7EJwDqtQnjhE7ME23q6ngw== +tslib@^1.9.3: + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + vscode-textmate@^4.1.1: version "4.2.2" resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.2.2.tgz#0b4dabc69a6fba79a065cb6b615f66eac07c8f4c" diff --git a/remote/yarn.lock b/remote/yarn.lock index 019b237911..b271a9fd5c 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -2,6 +2,69 @@ # yarn lockfile v1 +"@microsoft/applicationinsights-analytics-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-2.1.1.tgz#6d09c1915f808026e2d45165d04802f09affed59" + integrity sha512-VKIutoFKY99CyKwxLUuj6Vnq14/QwXo9/QSQDpYnHEjo+uKn7QmLsHqWw0K9uYNfNAXt4BZimX/zDg6jZtzeXg== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-channel-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.1.1.tgz#e205eddd93e49d17d9e0711a612b4bfc9810888f" + integrity sha512-fYr9IAqtaEr9AmaPaL3SLQVT3t3GQzl+n74gpNKyAVakDIm0nYQ/bimjdcAhJMDf1VGNSPg/xICneyuZg7Wxlg== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-common@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-common/-/applicationinsights-common-2.1.1.tgz#27e6074584a7a3a8ca3f11f7ff2b7ff0f395bf2d" + integrity sha512-2hkS1Ia1FmAjCuYZ5JlG20/WgObqdsKtmK5YALAFGHIB4KSQ/Za1qazS+7GsG+E0F9UJivNWL1geUIcNqg5Qjg== + dependencies: + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-core-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.1.1.tgz#30fb6a519cc1c6119c419c4811ce72c260217d9e" + integrity sha512-4t4wf6SKqIcWEQDPg/uOhm+BxtHhu/AFreyEoYZmMfcxzAu33h1FtTQRtxBNbYH1+thiNZCh80yUpnT7d9Hrlw== + dependencies: + tslib "^1.9.3" + +"@microsoft/applicationinsights-dependencies-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-2.1.1.tgz#8154c3efcb24617d015d0bce7c2cc47797a8d3c4" + integrity sha512-yhb4EToBp+aI+qLo0h5NDNtoo3sDFV60uyIOK843YjzXqVotcXX/lRShlghTkJtYH09QhrdzDjViUHnD4sMFSQ== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-properties-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-2.1.1.tgz#ca34232766eb16167b5d87693e2ae5d94f2a1559" + integrity sha512-8l+/ppw6xKTam2RL4EHZ52Lcf217olw81j6kyBNKtIcGwSnLNHrFwEeF3vBWIteG2JKzlg1GhGjrkB3oxXsV2g== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-web@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web/-/applicationinsights-web-2.1.1.tgz#1a44eddda7c244b88d9eb052dab6c855682e4f05" + integrity sha512-crvhCkNsNxkFuPWmttyWNSAA96D5FxBtKS6UA9MV9f9XHevTfchf/E3AuU9JZcsXufWMQLwLrUQ9ZiA1QJ0EWA== + dependencies: + "@microsoft/applicationinsights-analytics-js" "2.1.1" + "@microsoft/applicationinsights-channel-js" "2.1.1" + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + "@microsoft/applicationinsights-dependencies-js" "2.1.1" + "@microsoft/applicationinsights-properties-js" "2.1.1" + agent-base@4, agent-base@^4.1.0: version "4.2.0" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce" @@ -660,11 +723,6 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -minimist@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= - mixin-deep@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" @@ -1029,6 +1087,11 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" +tslib@^1.9.3: + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + typechecker@^4.3.0: version "4.7.0" resolved "https://registry.yarnpkg.com/typechecker/-/typechecker-4.7.0.tgz#5249f427358f45b7250c4924fd4d01ed9ba435e9" @@ -1113,6 +1176,11 @@ vscode-fsevents@1.2.12: dependencies: nan "^2.14.0" +vscode-minimist@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/vscode-minimist/-/vscode-minimist-1.2.1.tgz#e63d3f4a9bf3680dcb8f9304eed612323fd6926a" + integrity sha512-cmB72+qDoiCFJ1UKnGUBdGYfXzdpJ3bQM/D/+XhkVk5v7uZgLbYiCz5JcwVyk7NC7hSi5VGtQ4wihzmi12NeXw== + vscode-proxy-agent@0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.4.0.tgz#574833e65405c6333f350f1b9fef9909deccb6b5" @@ -1123,10 +1191,10 @@ vscode-proxy-agent@0.4.0: https-proxy-agent "2.2.1" socks-proxy-agent "4.0.1" -vscode-ripgrep@^1.5.5: - version "1.5.5" - resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.5.tgz#24c0e9cb356cf889c98e15ecb58f9cf654a1d961" - integrity sha512-OrPrAmcun4+uZAuNcQvE6CCPskh+5AsjANod/Q3zRcJcGNxgoOSGlQN9RPtatkUNmkN8Nn8mZBnS1jMylu/dKg== +vscode-ripgrep@^1.5.6: + version "1.5.6" + resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.6.tgz#93bf5c99ca5f8248950a305e224f6ca153c30af4" + integrity sha512-WRIM9XpUj6dsfdAmuI3ANbmT1ysPUVsYy/2uCLDHJa9kbiB4T7uGvFnnc0Rgx2qQnyRAwL7PeWaFgUljPPxf2g== vscode-textmate@^4.2.2: version "4.2.2" @@ -1159,10 +1227,10 @@ xterm-addon-web-links@0.1.0-beta10: resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.1.0-beta10.tgz#610fa9773a2a5ccd41c1c83ba0e2dd2c9eb66a23" integrity sha512-xfpjy0V6bB4BR44qIgZQPoCMVakxb65gMscPkHpO//QxvUxKzabV3dxOsIbeZRFkUGsWTFlvz2OoaBLoNtv5gg== -xterm@3.15.0-beta90: - version "3.15.0-beta90" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta90.tgz#e1732c2914584c86cffa797ba762c482f21ce182" - integrity sha512-eixIA5brfoez+Y8bJPCcIw8Q7LgOvxRX3cPBaGmo7ozUASx9IEGOmvIRQX1ozTUmxEiXPnAxOfl/isQ+yNlaww== +xterm@3.15.0-beta98: + version "3.15.0-beta98" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta98.tgz#37f37c35577422880e7ef673cc37f9d2a45dd40c" + integrity sha512-vZbg2LcRvoiJOgr1MyeLFM9mF4uib3BWUWDHyFc+vZ58CTuK0iczOvFXgk/ySo23ZLqwmHQSigLgmWvZ8J5G0Q== yauzl@^2.9.2: version "2.10.0" diff --git a/resources/completions/bash/code b/resources/completions/bash/code index f40963db05..c9e4b16730 100644 --- a/resources/completions/bash/code +++ b/resources/completions/bash/code @@ -1,4 +1,4 @@ -_code() +_@@APPNAME@@() { local cur prev words cword split _init_completion -s || return @@ -58,4 +58,4 @@ _code() _filedir } && -complete -F _code code +complete -F _@@APPNAME@@ @@APPNAME@@ diff --git a/resources/completions/zsh/_code b/resources/completions/zsh/_code index 265c199f62..25dfe7ca98 100644 --- a/resources/completions/zsh/_code +++ b/resources/completions/zsh/_code @@ -1,4 +1,4 @@ -#compdef code +#compdef @@APPNAME@@ local arguments @@ -14,7 +14,7 @@ arguments=( '--user-data-dir[specify the directory that user data is kept in]:directory:_directories' '(- *)'{-v,--version}'[print version]' '(- *)'{-h,--help}'[print usage]' - '(- *)'{--telemetry}'[Shows all telemetry events which VS code collects.]' + '--telemetry[show all telemetry events which VS code collects]' '--extensions-dir[set the root path for extensions]:root path:_directories' '--list-extensions[list the installed extensions]' '--category[filters instaled extension list by category, when using --list-extension]' diff --git a/resources/linux/rpm/code.spec.template b/resources/linux/rpm/code.spec.template index c942d65a74..93e20cd482 100644 --- a/resources/linux/rpm/code.spec.template +++ b/resources/linux/rpm/code.spec.template @@ -18,14 +18,14 @@ Visual Studio Code is a new choice of tool that combines the simplicity of a cod mkdir -p %{buildroot}/usr/share/@@NAME@@ mkdir -p %{buildroot}/usr/share/applications mkdir -p %{buildroot}/usr/share/pixmaps -#mkdir -p %{buildroot}/usr/share/bash-completion/completions -#mkdir -p %{buildroot}/usr/share/zsh/site-functions +mkdir -p %{buildroot}/usr/share/bash-completion/completions +mkdir -p %{buildroot}/usr/share/zsh/site-functions cp -r usr/share/@@NAME@@/* %{buildroot}/usr/share/@@NAME@@ cp -r usr/share/applications/@@NAME@@.desktop %{buildroot}/usr/share/applications cp -r usr/share/applications/@@NAME@@-url-handler.desktop %{buildroot}/usr/share/applications cp -r usr/share/pixmaps/@@ICON@@.png %{buildroot}/usr/share/pixmaps -#cp usr/share/bash-completion/completions/code %{buildroot}/usr/share/bash-completion/completions/code -#cp usr/share/zsh/site-functions/_code %{buildroot}/usr/share/zsh/site-functions/_code +cp usr/share/bash-completion/completions/@@NAME@@ %{buildroot}/usr/share/bash-completion/completions/@@NAME@@ +cp usr/share/zsh/site-functions/_@@NAME@@ %{buildroot}/usr/share/zsh/site-functions/_@@NAME@@ %post # Remove the legacy bin command if this is the stable build @@ -58,5 +58,5 @@ fi /usr/share/applications/@@NAME@@.desktop /usr/share/applications/@@NAME@@-url-handler.desktop /usr/share/pixmaps/@@ICON@@.png -#/usr/share/bash-completion/completions/code -#/usr/share/zsh/site-functions/_code +/usr/share/bash-completion/completions/@@NAME@@ +/usr/share/zsh/site-functions/_@@NAME@@ diff --git a/src/buildfile.js b/src/buildfile.js index 143b506045..da10314e0a 100644 --- a/src/buildfile.js +++ b/src/buildfile.js @@ -3,6 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +function entrypoint (name) { + return [{ name: name, include: [], exclude: ['vs/css', 'vs/nls'] }]; +} + exports.base = [{ name: 'vs/base/common/worker/simpleWorker', include: ['vs/editor/common/services/editorSimpleWorker'], @@ -19,11 +23,15 @@ exports.serviceWorker = [{ dest: 'vs/workbench/contrib/resources/browser/resourceServiceWorkerMain.js' }]; -exports.workbench = require('./vs/workbench/buildfile').collectModules(['vs/workbench/workbench.main']); -exports.workbenchWeb = require('./vs/workbench/buildfile').collectModules(['vs/workbench/workbench.web.api']); +exports.workbench = require('./vs/workbench/buildfile').collectModules(['vs/workbench/workbench.desktop.main']); +exports.workbenchWeb = entrypoint('vs/workbench/workbench.web.api'); + +exports.keyboardMaps = [ + entrypoint('vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.linux'), + entrypoint('vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.darwin'), + entrypoint('vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.win') +]; exports.code = require('./vs/code/buildfile').collectModules(); -exports.entrypoint = function (name) { - return [{ name: name, include: [], exclude: ['vs/css', 'vs/nls'] }]; -}; +exports.entrypoint = entrypoint; diff --git a/src/main.js b/src/main.js index 4f51152ae1..d50913a265 100644 --- a/src/main.js +++ b/src/main.js @@ -133,7 +133,7 @@ function onReady() { } /** - * @typedef {import('minimist').ParsedArgs} ParsedArgs + * @typedef {{ [arg: string]: any; '--'?: string[]; _: string[]; }} ParsedArgs * * @param {ParsedArgs} cliArgs */ @@ -186,7 +186,7 @@ function getUserDataPath(cliArgs) { * @returns {ParsedArgs} */ function parseCLIArgs() { - const minimist = require('minimist'); + const minimist = require('vscode-minimist'); return minimist(process.argv, { string: [ diff --git a/src/sql/workbench/api/node/sqlExtHost.api.impl.ts b/src/sql/workbench/api/common/sqlExtHost.api.impl.ts similarity index 97% rename from src/sql/workbench/api/node/sqlExtHost.api.impl.ts rename to src/sql/workbench/api/common/sqlExtHost.api.impl.ts index 7843fef47a..77f96832f2 100644 --- a/src/sql/workbench/api/node/sqlExtHost.api.impl.ts +++ b/src/sql/workbench/api/common/sqlExtHost.api.impl.ts @@ -3,9 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as extHostApi from 'vs/workbench/api/node/extHost.api.impl'; -import { IInitData, IMainContext } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; +import * as extHostApi from 'vs/workbench/api/common/extHost.api.impl'; import { URI } from 'vs/base/common/uri'; import * as azdata from 'azdata'; @@ -35,13 +33,15 @@ import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/c import { ExtHostExtensionManagement } from 'sql/workbench/api/common/extHostExtensionManagement'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { TernarySearchTree } from 'vs/base/common/map'; -import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; -import { ExtHostConfiguration, ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; -import { ExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; +import { ExtHostConfigProvider, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; -import { IURITransformer } from 'vs/base/common/uriIpc'; import { mssqlProviderName } from 'sql/platform/connection/common/constants'; import { localize } from 'vs/nls'; +import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; +import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { ILogService } from 'vs/platform/log/common/log'; export interface ISqlExtensionApiFactory { vsCodeFactory(extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode; @@ -52,17 +52,12 @@ export interface ISqlExtensionApiFactory { /** * This method instantiates and returns the extension API surface */ -export function createApiFactory( - initData: IInitData, - rpcProtocol: IMainContext, - extHostWorkspace: ExtHostWorkspace, - extHostConfiguration: ExtHostConfiguration, - extensionService: ExtHostExtensionService, - logService: ExtHostLogService, - extHostStorage: ExtHostStorage, - uriTransformer: IURITransformer | null -): ISqlExtensionApiFactory { - let vsCodeFactory = extHostApi.createApiFactory(initData, rpcProtocol, extHostWorkspace, extHostConfiguration, extensionService, logService, extHostStorage, uriTransformer); +export function createApiFactory(accessor: ServicesAccessor): ISqlExtensionApiFactory { + const instaServer = accessor.get(IInstantiationService); + const uriTransformer = accessor.get(IURITransformerService); + const rpcProtocol = accessor.get(IExtHostRpcService); + const extHostLogService = accessor.get(ILogService); + let vsCodeFactory = instaServer.invokeFunction(extHostApi.createApiFactoryAndRegisterActors); // Addressable instances const extHostAccountManagement = rpcProtocol.set(SqlExtHostContext.ExtHostAccountManagement, new ExtHostAccountManagement(rpcProtocol)); @@ -72,7 +67,7 @@ export function createApiFactory( const extHostObjectExplorer = rpcProtocol.set(SqlExtHostContext.ExtHostObjectExplorer, new ExtHostObjectExplorer(rpcProtocol)); const extHostResourceProvider = rpcProtocol.set(SqlExtHostContext.ExtHostResourceProvider, new ExtHostResourceProvider(rpcProtocol)); const extHostModalDialogs = rpcProtocol.set(SqlExtHostContext.ExtHostModalDialogs, new ExtHostModalDialogs(rpcProtocol)); - const extHostTasks = rpcProtocol.set(SqlExtHostContext.ExtHostTasks, new ExtHostTasks(rpcProtocol, logService)); + const extHostTasks = rpcProtocol.set(SqlExtHostContext.ExtHostTasks, new ExtHostTasks(rpcProtocol, extHostLogService)); const extHostBackgroundTaskManagement = rpcProtocol.set(SqlExtHostContext.ExtHostBackgroundTaskManagement, new ExtHostBackgroundTaskManagement(rpcProtocol)); const extHostWebviewWidgets = rpcProtocol.set(SqlExtHostContext.ExtHostDashboardWebviews, new ExtHostDashboardWebviews(rpcProtocol)); const extHostModelViewTree = rpcProtocol.set(SqlExtHostContext.ExtHostModelViewTreeViews, new ExtHostModelViewTreeViews(rpcProtocol)); @@ -925,7 +920,7 @@ export function createApiFactory( }; } -export function initializeExtensionApi(extensionService: ExtHostExtensionService, apiFactory: ISqlExtensionApiFactory, extensionRegistry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): Promise { +export function initializeExtensionApi(extensionService: IExtHostExtensionService, apiFactory: ISqlExtensionApiFactory, extensionRegistry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): Promise { return extensionService.getExtensionPathIndex().then(trie => defineAPI(apiFactory, trie, extensionRegistry, configProvider)); } diff --git a/src/sql/workbench/browser/parts/views/customView.ts b/src/sql/workbench/browser/parts/views/customView.ts index a547a22be5..da2ff41961 100644 --- a/src/sql/workbench/browser/parts/views/customView.ts +++ b/src/sql/workbench/browser/parts/views/customView.ts @@ -263,12 +263,12 @@ export class CustomTreeView extends Disposable implements ITreeView { } } - private _message: string | IMarkdownString | undefined; - get message(): string | IMarkdownString | undefined { + private _message: string | undefined; + get message(): string | undefined { return this._message; } - set message(message: string | IMarkdownString | undefined) { + set message(message: string | undefined) { this._message = message; this.updateMessage(); } @@ -746,7 +746,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer // Definitions: https://github.com/electron/electron-typescript-definitions diff --git a/src/typings/vscode-minimist.d.ts b/src/typings/vscode-minimist.d.ts new file mode 100644 index 0000000000..17558a1a73 --- /dev/null +++ b/src/typings/vscode-minimist.d.ts @@ -0,0 +1,92 @@ +// Type definitions for minimist 1.2.0 +// Project: https://github.com/substack/minimist +// Definitions by: Bart van der Schoor , Necroskillz , kamranayub +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +/** + * Return an argument object populated with the array arguments from args + * + * @param args An optional argument array (typically `process.argv.slice(2)`) + * @param opts An optional options object to customize the parsing + */ +declare function minimist(args?: string[], opts?: minimist.Opts): minimist.ParsedArgs; + +/** + * Return an argument object populated with the array arguments from args. Strongly-typed + * to be the intersect of type T with minimist.ParsedArgs. + * + * @type T The type that will be intersected with minimist.ParsedArgs to represent the argument object + * @param args An optional argument array (typically `process.argv.slice(2)`) + * @param opts An optional options object to customize the parsing + */ +declare function minimist(args?: string[], opts?: minimist.Opts): T & minimist.ParsedArgs; + +/** + * Return an argument object populated with the array arguments from args. Strongly-typed + * to be the the type T which should extend minimist.ParsedArgs + * + * @type T The type that extends minimist.ParsedArgs and represents the argument object + * @param args An optional argument array (typically `process.argv.slice(2)`) + * @param opts An optional options object to customize the parsing + */ +declare function minimist(args?: string[], opts?: minimist.Opts): T; + +declare namespace minimist { + export interface Opts { + /** + * A string or array of strings argument names to always treat as strings + */ + string?: string | string[]; + + /** + * A boolean, string or array of strings to always treat as booleans. If true will treat + * all double hyphenated arguments without equals signs as boolean (e.g. affects `--foo`, not `-f` or `--foo=bar`) + */ + boolean?: boolean | string | string[]; + + /** + * An object mapping string names to strings or arrays of string argument names to use as aliases + */ + alias?: { [key: string]: string | string[] }; + + /** + * An object mapping string argument names to default values + */ + default?: { [key: string]: any }; + + /** + * When true, populate argv._ with everything after the first non-option + */ + stopEarly?: boolean; + + /** + * A function which is invoked with a command line parameter not defined in the opts + * configuration object. If the function returns false, the unknown option is not added to argv + */ + unknown?: (arg: string) => boolean; + + /** + * When true, populate argv._ with everything before the -- and argv['--'] with everything after the --. + * Note that with -- set, parsing for arguments still stops after the `--`. + */ + '--'?: boolean; + } + + export interface ParsedArgs { + [arg: string]: any; + + /** + * If opts['--'] is true, populated with everything after the -- + */ + '--'?: string[]; + + /** + * Contains all the arguments that didn't have an option associated with them + */ + _: string[]; + } +} + +declare module "vscode-minimist" { + export = minimist; +} diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index c2c111017a..d683c99d55 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -56,7 +56,7 @@ class WindowManager { } // --- Fullscreen - private _fullscreen: boolean; + private _fullscreen: boolean = false; private readonly _onDidChangeFullscreen = new Emitter(); public readonly onDidChangeFullscreen: Event = this._onDidChangeFullscreen.event; diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index f9211cfd2a..d390282a78 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -50,8 +50,8 @@ interface IDomClassList { const _manualClassList = new class implements IDomClassList { - private _lastStart: number; - private _lastEnd: number; + private _lastStart: number = -1; + private _lastEnd: number = -1; private _findClassName(node: HTMLElement, className: string): void { @@ -1200,7 +1200,7 @@ export function asDomUri(uri: URI): URI { if (Schemas.vscodeRemote === uri.scheme) { // rewrite vscode-remote-uris to uris of the window location // so that they can be intercepted by the service worker - return _location.with({ path: '/vscode-resources/fetch', query: `u=${JSON.stringify(uri)}` }); + return _location.with({ path: '/vscode-remote', query: JSON.stringify(uri) }); } return uri; } diff --git a/src/vs/base/browser/touch.ts b/src/vs/base/browser/touch.ts index 34cee89d67..ae39dbf3e1 100644 --- a/src/vs/base/browser/touch.ts +++ b/src/vs/base/browser/touch.ts @@ -69,7 +69,7 @@ export class Gesture extends Disposable { private static INSTANCE: Gesture; private static HOLD_DELAY = 700; - private dispatched: boolean; + private dispatched = false; private targets: HTMLElement[]; private handle: IDisposable | null; diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 52c15c9652..9e29b0ed7b 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -37,7 +37,7 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { _context: any; _action: IAction; - private _actionRunner: IActionRunner; + private _actionRunner!: IActionRunner; constructor(context: any, action: IAction, protected options?: IBaseActionViewItemOptions) { super(); @@ -232,7 +232,7 @@ export interface IActionViewItemOptions extends IBaseActionViewItemOptions { export class ActionViewItem extends BaseActionViewItem { - protected label: HTMLElement; + protected label!: HTMLElement; protected options: IActionViewItemOptions; private cssClass?: string; diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index ae2a205025..a3b6a7cc90 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -77,8 +77,8 @@ export class BreadcrumbsWidget { private _focusedItemIdx: number = -1; private _selectedItemIdx: number = -1; - private _pendingLayout: IDisposable; - private _dimension: dom.Dimension; + private _pendingLayout: IDisposable | undefined; + private _dimension: dom.Dimension | undefined; constructor( container: HTMLElement diff --git a/src/vs/base/browser/ui/centered/centeredViewLayout.ts b/src/vs/base/browser/ui/centered/centeredViewLayout.ts index ba51c4c739..a6affaab3e 100644 --- a/src/vs/base/browser/ui/centered/centeredViewLayout.ts +++ b/src/vs/base/browser/ui/centered/centeredViewLayout.ts @@ -20,10 +20,12 @@ const GOLDEN_RATIO = { rightMarginRatio: 0.1909 }; -function createEmptyView(background: Color): ISplitViewView { +function createEmptyView(background: Color | undefined): ISplitViewView { const element = $('.centered-layout-margin'); element.style.height = '100%'; - element.style.backgroundColor = background.toString(); + if (background) { + element.style.backgroundColor = background.toString(); + } return { element, @@ -53,7 +55,7 @@ export class CenteredViewLayout implements IDisposable { private splitView?: SplitView; private width: number = 0; private height: number = 0; - private style: ICenteredViewStyles; + private style!: ICenteredViewStyles; private didLayout = false; private emptyViews: ISplitViewView[] | undefined; private readonly splitViewDisposables = new DisposableStore(); @@ -132,7 +134,8 @@ export class CenteredViewLayout implements IDisposable { this.splitView.layout(this.width); this.splitView.addView(toSplitViewView(this.view, () => this.height), 0); - this.emptyViews = [createEmptyView(this.style.background), createEmptyView(this.style.background)]; + const backgroundColor = this.style ? this.style.background : undefined; + this.emptyViews = [createEmptyView(backgroundColor), createEmptyView(backgroundColor)]; this.splitView.addView(this.emptyViews[0], this.state.leftMarginRatio * this.width, 0); this.splitView.addView(this.emptyViews[1], this.state.rightMarginRatio * this.width, 2); } else { diff --git a/src/vs/base/browser/ui/checkbox/check-dark.svg b/src/vs/base/browser/ui/checkbox/check-dark.svg new file mode 100644 index 0000000000..865cc83c34 --- /dev/null +++ b/src/vs/base/browser/ui/checkbox/check-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/base/browser/ui/checkbox/check-light.svg b/src/vs/base/browser/ui/checkbox/check-light.svg new file mode 100644 index 0000000000..e1a546660e --- /dev/null +++ b/src/vs/base/browser/ui/checkbox/check-light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/base/browser/ui/checkbox/checkbox.css b/src/vs/base/browser/ui/checkbox/checkbox.css index e40b0c406a..9e592e1e20 100644 --- a/src/vs/base/browser/ui/checkbox/checkbox.css +++ b/src/vs/base/browser/ui/checkbox/checkbox.css @@ -39,4 +39,24 @@ .hc-black .monaco-custom-checkbox:hover { background: none; -} \ No newline at end of file +} + +.monaco-custom-checkbox.monaco-simple-checkbox { + height: 18px; + width: 18px; + border: 1px solid transparent; + border-radius: 3px; + margin-right: 9px; + margin-left: 0px; + padding: 0px; + opacity: 1; + background-size: 16px !important; +} + +.monaco-custom-checkbox.monaco-simple-checkbox.checked { + background: url('check-light.svg') center center no-repeat; +} + +.monaco-custom-checkbox.monaco-simple-checkbox.checked { + background: url('check-dark.svg') center center no-repeat; +} diff --git a/src/vs/base/browser/ui/checkbox/checkbox.ts b/src/vs/base/browser/ui/checkbox/checkbox.ts index 25204c70b9..0f609f880f 100644 --- a/src/vs/base/browser/ui/checkbox/checkbox.ts +++ b/src/vs/base/browser/ui/checkbox/checkbox.ts @@ -25,6 +25,12 @@ export interface ICheckboxStyles { inputActiveOptionBackground?: Color; } +export interface ISimpleCheckboxStyles { + checkboxBackground?: Color; + checkboxBorder?: Color; + checkboxForeground?: Color; +} + const defaultOpts = { inputActiveOptionBorder: Color.fromHex('#007ACC00'), inputActiveOptionBackground: Color.fromHex('#0E639C50') @@ -32,7 +38,7 @@ const defaultOpts = { export class CheckboxActionViewItem extends BaseActionViewItem { - private checkbox: Checkbox; + private checkbox!: Checkbox; private readonly disposables = new DisposableStore(); render(container: HTMLElement): void { @@ -45,7 +51,7 @@ export class CheckboxActionViewItem extends BaseActionViewItem { title: this._action.label }); this.disposables.add(this.checkbox); - this.disposables.add(this.checkbox.onChange(() => this._action.checked = this.checkbox.checked, this)); + this.disposables.add(this.checkbox.onChange(() => this._action.checked = this.checkbox!.checked, this)); this.element.appendChild(this.checkbox.domNode); } @@ -174,3 +180,46 @@ export class Checkbox extends Widget { this.domNode.setAttribute('aria-disabled', String(true)); } } + +export class SimpleCheckbox extends Widget { + private checkbox: Checkbox; + private styles: ISimpleCheckboxStyles; + + readonly domNode: HTMLElement; + + constructor(private title: string, private isChecked: boolean) { + super(); + + this.checkbox = new Checkbox({ title: this.title, isChecked: this.isChecked, actionClassName: 'monaco-simple-checkbox' }); + + this.domNode = this.checkbox.domNode; + + this.styles = {}; + + this.checkbox.onChange(() => { + this.applyStyles(); + }); + } + + get checked(): boolean { + return this.checkbox.checked; + } + + set checked(newIsChecked: boolean) { + this.checkbox.checked = newIsChecked; + + this.applyStyles(); + } + + style(styles: ISimpleCheckboxStyles): void { + this.styles = styles; + + this.applyStyles(); + } + + protected applyStyles(): void { + this.domNode.style.color = this.styles.checkboxForeground ? this.styles.checkboxForeground.toString() : null; + this.domNode.style.backgroundColor = this.styles.checkboxBackground ? this.styles.checkboxBackground.toString() : null; + this.domNode.style.borderColor = this.styles.checkboxBorder ? this.styles.checkboxBorder.toString() : null; + } +} diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index 5fca8286bb..8377d6bcdd 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -5,7 +5,7 @@ import 'vs/css!./contextview'; import * as DOM from 'vs/base/browser/dom'; -import { IDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Range } from 'vs/base/common/range'; export interface IAnchor { @@ -100,11 +100,11 @@ export class ContextView extends Disposable { private static readonly BUBBLE_UP_EVENTS = ['click', 'keydown', 'focus', 'blur']; private static readonly BUBBLE_DOWN_EVENTS = ['click']; - private container: HTMLElement | null; + private container: HTMLElement | null = null; private view: HTMLElement; - private delegate: IDelegate | null; - private toDisposeOnClean: IDisposable | null; - private toDisposeOnSetContainer: IDisposable; + private delegate: IDelegate | null = null; + private toDisposeOnClean: IDisposable = Disposable.None; + private toDisposeOnSetContainer: IDisposable = Disposable.None; constructor(container: HTMLElement) { super(); @@ -120,7 +120,7 @@ export class ContextView extends Disposable { setContainer(container: HTMLElement | null): void { if (this.container) { - dispose(this.toDisposeOnSetContainer); + this.toDisposeOnSetContainer.dispose(); this.container.removeChild(this.view); this.container = null; } @@ -159,7 +159,7 @@ export class ContextView extends Disposable { DOM.show(this.view); // Render content - this.toDisposeOnClean = delegate.render(this.view); + this.toDisposeOnClean = delegate.render(this.view) || Disposable.None; // Set active delegate this.delegate = delegate; @@ -267,10 +267,7 @@ export class ContextView extends Disposable { delegate.onHide(data); } - if (this.toDisposeOnClean) { - this.toDisposeOnClean.dispose(); - this.toDisposeOnClean = null; - } + this.toDisposeOnClean.dispose(); DOM.hide(this.view); } diff --git a/src/vs/base/browser/ui/countBadge/countBadge.ts b/src/vs/base/browser/ui/countBadge/countBadge.ts index edcce1b5ad..a1e1b58230 100644 --- a/src/vs/base/browser/ui/countBadge/countBadge.ts +++ b/src/vs/base/browser/ui/countBadge/countBadge.ts @@ -29,7 +29,7 @@ const defaultOpts = { export class CountBadge { private element: HTMLElement; - private count: number; + private count: number = 0; private countFormat: string; private titleFormat: string; diff --git a/src/vs/base/browser/ui/dialog/dialog.css b/src/vs/base/browser/ui/dialog/dialog.css index 9c736c3b20..b8e657ee1d 100644 --- a/src/vs/base/browser/ui/dialog/dialog.css +++ b/src/vs/base/browser/ui/dialog/dialog.css @@ -149,6 +149,11 @@ outline-style: solid; } +.monaco-workbench .dialog-box .dialog-message-row .dialog-message-container .dialog-checkbox-row { + padding: 15px 0px 0px; + display: flex; +} + /** Dialog: Buttons Row */ .monaco-workbench .dialog-box > .dialog-buttons-row { display: flex; @@ -175,4 +180,4 @@ margin: 4px 5px; /* allows button focus outline to be visible */ overflow: hidden; text-overflow: ellipsis; -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts index 4d3d1e59ff..a367ebe6ff 100644 --- a/src/vs/base/browser/ui/dialog/dialog.ts +++ b/src/vs/base/browser/ui/dialog/dialog.ts @@ -16,15 +16,23 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { isMacintosh, isLinux } from 'vs/base/common/platform'; +import { SimpleCheckbox, ISimpleCheckboxStyles } from 'vs/base/browser/ui/checkbox/checkbox'; export interface IDialogOptions { cancelId?: number; detail?: string; + checkboxLabel?: string; + checkboxChecked?: boolean; type?: 'none' | 'info' | 'error' | 'question' | 'warning' | 'pending'; keyEventProcessor?: (event: StandardKeyboardEvent) => void; } -export interface IDialogStyles extends IButtonStyles { +export interface IDialogResult { + button: number; + checkboxChecked?: boolean; +} + +export interface IDialogStyles extends IButtonStyles, ISimpleCheckboxStyles { dialogForeground?: Color; dialogBackground?: Color; dialogShadow?: Color; @@ -42,6 +50,7 @@ export class Dialog extends Disposable { private buttonsContainer: HTMLElement | undefined; private messageDetailElement: HTMLElement | undefined; private iconElement: HTMLElement | undefined; + private checkbox: SimpleCheckbox | undefined; private toolbarContainer: HTMLElement | undefined; private buttonGroup: ButtonGroup | undefined; private styles: IDialogStyles | undefined; @@ -68,6 +77,19 @@ export class Dialog extends Disposable { this.messageDetailElement = messageContainer.appendChild($('.dialog-message-detail')); this.messageDetailElement.innerText = this.options.detail ? this.options.detail : message; + if (this.options.checkboxLabel) { + const checkboxRowElement = messageContainer.appendChild($('.dialog-checkbox-row')); + + this.checkbox = this._register(new SimpleCheckbox(this.options.checkboxLabel, !!this.options.checkboxChecked)); + + checkboxRowElement.appendChild(this.checkbox.domNode); + + const checkboxMessageElement = checkboxRowElement.appendChild($('.dialog-checkbox-message')); + checkboxMessageElement.innerText = this.options.checkboxLabel; + } + + + const toolbarRowElement = this.element.appendChild($('.dialog-toolbar-row')); this.toolbarContainer = toolbarRowElement.appendChild($('.dialog-toolbar')); } @@ -78,12 +100,12 @@ export class Dialog extends Disposable { } } - async show(): Promise { + async show(): Promise { this.focusToReturn = document.activeElement as HTMLElement; - return new Promise((resolve) => { + return new Promise((resolve) => { if (!this.element || !this.buttonsContainer || !this.iconElement || !this.toolbarContainer) { - resolve(0); + resolve({ button: 0 }); return; } @@ -112,7 +134,7 @@ export class Dialog extends Disposable { this._register(button.onDidClick(e => { EventHelper.stop(e); - resolve(buttonMap[index].index); + resolve({ button: buttonMap[index].index, checkboxChecked: this.checkbox ? this.checkbox.checked : undefined }); })); }); @@ -147,7 +169,7 @@ export class Dialog extends Disposable { const evt = new StandardKeyboardEvent(e); if (evt.equals(KeyCode.Escape)) { - resolve(this.options.cancelId || 0); + resolve({ button: this.options.cancelId || 0, checkboxChecked: this.checkbox ? this.checkbox.checked : undefined }); } })); @@ -187,7 +209,7 @@ export class Dialog extends Disposable { const actionBar = new ActionBar(this.toolbarContainer, {}); const action = new Action('dialog.close', nls.localize('dialogClose', "Close Dialog"), 'dialog-close-action', true, () => { - resolve(this.options.cancelId || 0); + resolve({ button: this.options.cancelId || 0, checkboxChecked: this.checkbox ? this.checkbox.checked : undefined }); return Promise.resolve(); }); @@ -221,6 +243,10 @@ export class Dialog extends Disposable { if (this.buttonGroup) { this.buttonGroup.buttons.forEach(button => button.style(style)); } + + if (this.checkbox) { + this.checkbox.style(style); + } } } } @@ -261,4 +287,4 @@ export class Dialog extends Disposable { return buttonMap; } -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/dropdown/dropdown.ts b/src/vs/base/browser/ui/dropdown/dropdown.ts index 296fa86c0e..21788bc0ca 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -29,7 +29,7 @@ export class BaseDropdown extends ActionRunner { private boxContainer?: HTMLElement; private _label?: HTMLElement; private contents?: HTMLElement; - private visible: boolean; + private visible: boolean | undefined; constructor(container: HTMLElement, options: IBaseDropdownOptions) { super(); @@ -109,7 +109,7 @@ export class BaseDropdown extends ActionRunner { } isVisible(): boolean { - return this.visible; + return !!this.visible; } protected onEvent(e: Event, activeElement: HTMLElement): void { @@ -272,7 +272,7 @@ export class DropdownMenu extends BaseDropdown { export class DropdownMenuActionViewItem extends BaseActionViewItem { private menuActionsOrProvider: any; - private dropdownMenu: DropdownMenu; + private dropdownMenu: DropdownMenu | undefined; private contextMenuProvider: IContextMenuProvider; private actionViewItemProvider?: IActionViewItemProvider; private keybindings?: (action: IAction) => ResolvedKeybinding | undefined; @@ -281,7 +281,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { constructor(action: IAction, menuActions: ReadonlyArray, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment); constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment); - constructor(action: IAction, menuActionsOrProvider: any, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment) { + constructor(action: IAction, menuActionsOrProvider: ReadonlyArray | IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment) { super(null, action); this.menuActionsOrProvider = menuActionsOrProvider; diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index d582f39d06..09fc7be707 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -114,7 +114,130 @@ export class FindInput extends Widget { this.inputValidationErrorBackground = options.inputValidationErrorBackground; this.inputValidationErrorForeground = options.inputValidationErrorForeground; - this.buildDomNode(options.appendCaseSensitiveLabel || '', options.appendWholeWordsLabel || '', options.appendRegexLabel || '', options.history || [], !!options.flexibleHeight); + const appendCaseSensitiveLabel = options.appendCaseSensitiveLabel || ''; + const appendWholeWordsLabel = options.appendWholeWordsLabel || ''; + const appendRegexLabel = options.appendRegexLabel || ''; + const history = options.history || []; + const flexibleHeight = !!options.flexibleHeight; + + this.domNode = document.createElement('div'); + dom.addClass(this.domNode, 'monaco-findInput'); + + this.inputBox = this._register(new HistoryInputBox(this.domNode, this.contextViewProvider, { + placeholder: this.placeholder || '', + ariaLabel: this.label || '', + validationOptions: { + validation: this.validation + }, + inputBackground: this.inputBackground, + inputForeground: this.inputForeground, + inputBorder: this.inputBorder, + inputValidationInfoBackground: this.inputValidationInfoBackground, + inputValidationInfoForeground: this.inputValidationInfoForeground, + inputValidationInfoBorder: this.inputValidationInfoBorder, + inputValidationWarningBackground: this.inputValidationWarningBackground, + inputValidationWarningForeground: this.inputValidationWarningForeground, + inputValidationWarningBorder: this.inputValidationWarningBorder, + inputValidationErrorBackground: this.inputValidationErrorBackground, + inputValidationErrorForeground: this.inputValidationErrorForeground, + inputValidationErrorBorder: this.inputValidationErrorBorder, + history, + flexibleHeight + })); + + this.regex = this._register(new RegexCheckbox({ + appendTitle: appendRegexLabel, + isChecked: false, + inputActiveOptionBorder: this.inputActiveOptionBorder, + inputActiveOptionBackground: this.inputActiveOptionBackground + })); + this._register(this.regex.onChange(viaKeyboard => { + this._onDidOptionChange.fire(viaKeyboard); + if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { + this.inputBox.focus(); + } + this.validate(); + })); + this._register(this.regex.onKeyDown(e => { + this._onRegexKeyDown.fire(e); + })); + + this.wholeWords = this._register(new WholeWordsCheckbox({ + appendTitle: appendWholeWordsLabel, + isChecked: false, + inputActiveOptionBorder: this.inputActiveOptionBorder, + inputActiveOptionBackground: this.inputActiveOptionBackground + })); + this._register(this.wholeWords.onChange(viaKeyboard => { + this._onDidOptionChange.fire(viaKeyboard); + if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { + this.inputBox.focus(); + } + this.validate(); + })); + + this.caseSensitive = this._register(new CaseSensitiveCheckbox({ + appendTitle: appendCaseSensitiveLabel, + isChecked: false, + inputActiveOptionBorder: this.inputActiveOptionBorder, + inputActiveOptionBackground: this.inputActiveOptionBackground + })); + this._register(this.caseSensitive.onChange(viaKeyboard => { + this._onDidOptionChange.fire(viaKeyboard); + if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { + this.inputBox.focus(); + } + this.validate(); + })); + this._register(this.caseSensitive.onKeyDown(e => { + this._onCaseSensitiveKeyDown.fire(e); + })); + + if (this._showOptionButtons) { + const paddingRight = (this.caseSensitive.width() + this.wholeWords.width() + this.regex.width()) + 'px'; + this.inputBox.inputElement.style.paddingRight = paddingRight; + if (this.inputBox.mirrorElement) { + this.inputBox.mirrorElement.style.paddingRight = paddingRight; + } + } + + // Arrow-Key support to navigate between options + let indexes = [this.caseSensitive.domNode, this.wholeWords.domNode, this.regex.domNode]; + this.onkeydown(this.domNode, (event: IKeyboardEvent) => { + if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Escape)) { + let index = indexes.indexOf(document.activeElement); + if (index >= 0) { + let newIndex: number = -1; + if (event.equals(KeyCode.RightArrow)) { + newIndex = (index + 1) % indexes.length; + } else if (event.equals(KeyCode.LeftArrow)) { + if (index === 0) { + newIndex = indexes.length - 1; + } else { + newIndex = index - 1; + } + } + + if (event.equals(KeyCode.Escape)) { + indexes[index].blur(); + } else if (newIndex >= 0) { + indexes[newIndex].focus(); + } + + dom.EventHelper.stop(event, true); + } + } + }); + + + let controls = document.createElement('div'); + controls.className = 'controls'; + controls.style.display = this._showOptionButtons ? 'block' : 'none'; + controls.appendChild(this.caseSensitive.domNode); + controls.appendChild(this.wholeWords.domNode); + controls.appendChild(this.regex.domNode); + + this.domNode.appendChild(controls); if (parent) { parent.appendChild(this.domNode); @@ -270,127 +393,6 @@ export class FindInput extends Widget { dom.addClass(this.domNode, 'highlight-' + (this._lastHighlightFindOptions)); } - private buildDomNode(appendCaseSensitiveLabel: string, appendWholeWordsLabel: string, appendRegexLabel: string, history: string[], flexibleHeight: boolean): void { - this.domNode = document.createElement('div'); - dom.addClass(this.domNode, 'monaco-findInput'); - - this.inputBox = this._register(new HistoryInputBox(this.domNode, this.contextViewProvider, { - placeholder: this.placeholder || '', - ariaLabel: this.label || '', - validationOptions: { - validation: this.validation - }, - inputBackground: this.inputBackground, - inputForeground: this.inputForeground, - inputBorder: this.inputBorder, - inputValidationInfoBackground: this.inputValidationInfoBackground, - inputValidationInfoForeground: this.inputValidationInfoForeground, - inputValidationInfoBorder: this.inputValidationInfoBorder, - inputValidationWarningBackground: this.inputValidationWarningBackground, - inputValidationWarningForeground: this.inputValidationWarningForeground, - inputValidationWarningBorder: this.inputValidationWarningBorder, - inputValidationErrorBackground: this.inputValidationErrorBackground, - inputValidationErrorForeground: this.inputValidationErrorForeground, - inputValidationErrorBorder: this.inputValidationErrorBorder, - history, - flexibleHeight - })); - - this.regex = this._register(new RegexCheckbox({ - appendTitle: appendRegexLabel, - isChecked: false, - inputActiveOptionBorder: this.inputActiveOptionBorder, - inputActiveOptionBackground: this.inputActiveOptionBackground - })); - this._register(this.regex.onChange(viaKeyboard => { - this._onDidOptionChange.fire(viaKeyboard); - if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { - this.inputBox.focus(); - } - this.validate(); - })); - this._register(this.regex.onKeyDown(e => { - this._onRegexKeyDown.fire(e); - })); - - this.wholeWords = this._register(new WholeWordsCheckbox({ - appendTitle: appendWholeWordsLabel, - isChecked: false, - inputActiveOptionBorder: this.inputActiveOptionBorder, - inputActiveOptionBackground: this.inputActiveOptionBackground - })); - this._register(this.wholeWords.onChange(viaKeyboard => { - this._onDidOptionChange.fire(viaKeyboard); - if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { - this.inputBox.focus(); - } - this.validate(); - })); - - this.caseSensitive = this._register(new CaseSensitiveCheckbox({ - appendTitle: appendCaseSensitiveLabel, - isChecked: false, - inputActiveOptionBorder: this.inputActiveOptionBorder, - inputActiveOptionBackground: this.inputActiveOptionBackground - })); - this._register(this.caseSensitive.onChange(viaKeyboard => { - this._onDidOptionChange.fire(viaKeyboard); - if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { - this.inputBox.focus(); - } - this.validate(); - })); - this._register(this.caseSensitive.onKeyDown(e => { - this._onCaseSensitiveKeyDown.fire(e); - })); - - if (this._showOptionButtons) { - const paddingRight = (this.caseSensitive.width() + this.wholeWords.width() + this.regex.width()) + 'px'; - this.inputBox.inputElement.style.paddingRight = paddingRight; - if (this.inputBox.mirrorElement) { - this.inputBox.mirrorElement.style.paddingRight = paddingRight; - } - } - - // Arrow-Key support to navigate between options - let indexes = [this.caseSensitive.domNode, this.wholeWords.domNode, this.regex.domNode]; - this.onkeydown(this.domNode, (event: IKeyboardEvent) => { - if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Escape)) { - let index = indexes.indexOf(document.activeElement); - if (index >= 0) { - let newIndex: number = -1; - if (event.equals(KeyCode.RightArrow)) { - newIndex = (index + 1) % indexes.length; - } else if (event.equals(KeyCode.LeftArrow)) { - if (index === 0) { - newIndex = indexes.length - 1; - } else { - newIndex = index - 1; - } - } - - if (event.equals(KeyCode.Escape)) { - indexes[index].blur(); - } else if (newIndex >= 0) { - indexes[newIndex].focus(); - } - - dom.EventHelper.stop(event, true); - } - } - }); - - - let controls = document.createElement('div'); - controls.className = 'controls'; - controls.style.display = this._showOptionButtons ? 'block' : 'none'; - controls.appendChild(this.caseSensitive.domNode); - controls.appendChild(this.wholeWords.domNode); - controls.appendChild(this.regex.domNode); - - this.domNode.appendChild(controls); - } - public validate(): void { if (this.inputBox) { this.inputBox.validate(); diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts index 46a19d6d82..75ef1241fc 100644 --- a/src/vs/base/browser/ui/grid/grid.ts +++ b/src/vs/base/browser/ui/grid/grid.ts @@ -277,6 +277,24 @@ export class Grid extends Disposable { this._addView(newView, viewSize, location); } + addViewAt(newView: T, size: number | DistributeSizing | InvisibleSizing, location: number[]): void { + if (this.views.has(newView)) { + throw new Error('Can\'t add same view twice'); + } + + let viewSize: number | GridViewSizing; + + if (typeof size === 'number') { + viewSize = size; + } else if (size.type === 'distribute') { + viewSize = GridViewSizing.Distribute; + } else { + viewSize = size; + } + + this._addView(newView, viewSize, location); + } + protected _addView(newView: T, size: number | GridViewSizing, location: number[]): void { this.views.set(newView, newView.element); this.gridview.addView(newView, size, location); @@ -308,6 +326,26 @@ export class Grid extends Disposable { } } + moveViewTo(view: T, location: number[]): void { + const sourceLocation = this.getViewLocation(view); + const [sourceParentLocation, from] = tail(sourceLocation); + const [targetParentLocation, to] = tail(location); + + if (equals(sourceParentLocation, targetParentLocation)) { + this.gridview.moveView(sourceParentLocation, from, to); + } else { + const size = this.getViewSize(view); + const orientation = getLocationOrientation(this.gridview.orientation, sourceLocation); + const cachedViewSize = this.getViewCachedVisibleSize(view); + const sizing = typeof cachedViewSize === 'undefined' + ? (orientation === Orientation.HORIZONTAL ? size.width : size.height) + : Sizing.Invisible(cachedViewSize); + + this.removeView(view); + this.addViewAt(view, sizing, location); + } + } + swapViews(from: T, to: T): void { const fromLocation = this.getViewLocation(from); const toLocation = this.getViewLocation(to); @@ -319,11 +357,20 @@ export class Grid extends Disposable { return this.gridview.resizeView(location, size); } - getViewSize(view: T): IViewSize { + getViewSize(view?: T): IViewSize { + if (!view) { + return this.gridview.getViewSize(); + } + const location = this.getViewLocation(view); return this.gridview.getViewSize(location); } + getViewCachedVisibleSize(view: T): number | undefined { + const location = this.getViewLocation(view); + return this.gridview.getViewCachedVisibleSize(location); + } + maximizeViewSize(view: T): void { const location = this.getViewLocation(view); this.gridview.maximizeViewSize(location); @@ -373,7 +420,7 @@ export class Grid extends Disposable { .map(node => node.view); } - private getViewLocation(view: T): number[] { + getViewLocation(view: T): number[] { const element = this.views.get(view); if (!element) { @@ -422,7 +469,7 @@ export interface ISerializableView extends IView { } export interface IViewDeserializer { - fromJSON(json: object | null): T; + fromJSON(json: any): T; } interface InitialLayoutContext { @@ -433,7 +480,7 @@ interface InitialLayoutContext { export interface ISerializedLeafNode { type: 'leaf'; - data: object | null; + data: any; size: number; visible?: boolean; } diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index dd01477127..930347184b 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -11,6 +11,7 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle' import { $ } from 'vs/base/browser/dom'; import { tail2 as tail } from 'vs/base/common/arrays'; import { Color } from 'vs/base/common/color'; +import { clamp } from 'vs/base/common/numbers'; export { Sizing, LayoutPriority } from 'vs/base/browser/ui/splitview/splitview'; export { Orientation } from 'vs/base/browser/ui/sash/sash'; @@ -277,9 +278,7 @@ class BranchNode implements ISplitView, IDisposable { throw new Error('Invalid from index'); } - if (to < 0 || to > this.children.length) { - throw new Error('Invalid to index'); - } + to = clamp(to, 0, this.children.length); if (from < to) { to--; @@ -300,9 +299,7 @@ class BranchNode implements ISplitView, IDisposable { throw new Error('Invalid from index'); } - if (to < 0 || to >= this.children.length) { - throw new Error('Invalid to index'); - } + to = clamp(to, 0, this.children.length); this.splitview.swapViews(from, to); [this.children[from].orthogonalStartSash, this.children[from].orthogonalEndSash, this.children[to].orthogonalStartSash, this.children[to].orthogonalEndSash] = [this.children[to].orthogonalStartSash, this.children[to].orthogonalEndSash, this.children[from].orthogonalStartSash, this.children[from].orthogonalEndSash]; @@ -351,6 +348,7 @@ class BranchNode implements ISplitView, IDisposable { } this.splitview.setViewVisible(index, visible); + this._onDidChange.fire(undefined); } getChildCachedVisibleSize(index: number): number | undefined { @@ -442,9 +440,6 @@ class LeafNode implements ISplitView, IDisposable { private _size: number = 0; get size(): number { return this._size; } - private _cachedVisibleSize: number | undefined; - get cachedVisibleSize(): number | undefined { return this._cachedVisibleSize; } - private _orthogonalSize: number; get orthogonalSize(): number { return this._orthogonalSize; } @@ -553,12 +548,6 @@ class LeafNode implements ISplitView, IDisposable { } setVisible(visible: boolean): void { - if (visible) { - this._cachedVisibleSize = undefined; - } else { - this._cachedVisibleSize = this._size; - } - if (this.view.setVisible) { this.view.setVisible(visible); } @@ -610,7 +599,7 @@ export class GridView implements IDisposable { private styles: IGridViewStyles; private proportionalLayout: boolean; - private _root: BranchNode; + private _root!: BranchNode; private onDidSashResetRelay = new Relay(); readonly onDidSashReset: Event = this.onDidSashResetRelay.event; @@ -765,6 +754,7 @@ export class GridView implements IDisposable { const [, parentIndex] = tail(rest); const sibling = parent.children[0]; + const isSiblingVisible = parent.isChildVisible(0); parent.removeChild(0); const sizes = grandParent.children.map((_, i) => grandParent.getChildSize(i)); @@ -779,7 +769,8 @@ export class GridView implements IDisposable { } } else { const newSibling = new LeafNode(sibling.view, orthogonal(sibling.orientation), this.layoutController, sibling.size); - grandParent.addChild(newSibling, sibling.orthogonalSize, parentIndex); + const sizing = isSiblingVisible ? sibling.orthogonalSize : Sizing.Invisible(sibling.orthogonalSize); + grandParent.addChild(newSibling, sizing, parentIndex); } for (let i = 0; i < sizes.length; i++) { @@ -868,11 +859,26 @@ export class GridView implements IDisposable { } } - getViewSize(location: number[]): IViewSize { + getViewSize(location?: number[]): IViewSize { + if (!location) { + return { width: this.root.width, height: this.root.height }; + } + const [, node] = this.getNode(location); return { width: node.width, height: node.height }; } + getViewCachedVisibleSize(location: number[]): number | undefined { + const [rest, index] = tail(location); + const [, parent] = this.getNode(rest); + + if (!(parent instanceof BranchNode)) { + throw new Error('Invalid location'); + } + + return parent.getChildCachedVisibleSize(index); + } + maximizeViewSize(location: number[]): void { const [ancestors, node] = this.getNode(location); @@ -929,12 +935,13 @@ export class GridView implements IDisposable { return this._getViews(node, this.orientation, { top: 0, left: 0, width: this.width, height: this.height }); } - private _getViews(node: Node, orientation: Orientation, box: Box): GridNode { + private _getViews(node: Node, orientation: Orientation, box: Box, cachedVisibleSize?: number): GridNode { if (node instanceof LeafNode) { - return { view: node.view, box, cachedVisibleSize: node.cachedVisibleSize }; + return { view: node.view, box, cachedVisibleSize }; } const children: GridNode[] = []; + let i = 0; let offset = 0; for (const child of node.children) { @@ -942,8 +949,9 @@ export class GridView implements IDisposable { const childBox: Box = orientation === Orientation.HORIZONTAL ? { top: box.top, left: box.left + offset, width: child.width, height: box.height } : { top: box.top + offset, left: box.left, width: box.width, height: child.height }; + const cachedVisibleSize = node.getChildCachedVisibleSize(i++); - children.push(this._getViews(child, childOrientation, childBox)); + children.push(this._getViews(child, childOrientation, childBox, cachedVisibleSize)); offset += orientation === Orientation.HORIZONTAL ? child.width : child.height; } diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index 0dbe6dece5..90f54f0e06 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -12,7 +12,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; export interface IIconLabelCreationOptions { supportHighlights?: boolean; supportDescriptionHighlights?: boolean; - donotSupportOcticons?: boolean; + supportOcticons?: boolean; } export interface IIconLabelValueOptions { @@ -100,13 +100,13 @@ export class IconLabel extends Disposable { this.labelDescriptionContainer = this._register(new FastLabelNode(dom.append(this.domNode.element, dom.$('.monaco-icon-label-description-container')))); if (options && options.supportHighlights) { - this.labelNode = new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name')), !options.donotSupportOcticons); + this.labelNode = new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name')), !!options.supportOcticons); } else { this.labelNode = this._register(new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name')))); } if (options && options.supportDescriptionHighlights) { - this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description')), !options.donotSupportOcticons); + this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description')), !!options.supportOcticons); } else { this.descriptionNodeFactory = () => this._register(new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description')))); } diff --git a/src/vs/base/browser/ui/inputbox/inputBox.css b/src/vs/base/browser/ui/inputbox/inputBox.css index 1ed73bb150..d429251c8f 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.css +++ b/src/vs/base/browser/ui/inputbox/inputBox.css @@ -58,7 +58,12 @@ .monaco-inputbox > .wrapper > textarea.input { display: block; - overflow: hidden; + -ms-overflow-style: none; /* IE 10+ */ + overflow: -moz-scrollbars-none; /* Firefox */ +} + +.monaco-inputbox > .wrapper > textarea.input::-webkit-scrollbar { + display: none; } .monaco-inputbox > .wrapper > .mirror { @@ -116,4 +121,4 @@ background-repeat: no-repeat; width: 16px; height: 16px; -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index 44fc80ba56..e3d30c3ad5 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -19,6 +19,9 @@ import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; import { HistoryNavigator } from 'vs/base/common/history'; import { IHistoryNavigationWidget } from 'vs/base/browser/history'; +import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; +import { ScrollbarVisibility } from 'vs/base/common/scrollable'; +import { domEvent } from 'vs/base/browser/event'; const $ = dom.$; @@ -28,6 +31,7 @@ export interface IInputOptions extends IInputBoxStyles { readonly type?: string; readonly validationOptions?: IInputValidationOptions; readonly flexibleHeight?: boolean; + readonly flexibleMaxHeight?: number; readonly actions?: ReadonlyArray; @@ -93,7 +97,6 @@ export class InputBox extends Widget { private contextViewProvider?: IContextViewProvider; element: HTMLElement; private input: HTMLInputElement; - private mirror: HTMLElement; private actionbar?: ActionBar; private options: IInputOptions; private message: IMessage | null; @@ -101,7 +104,12 @@ export class InputBox extends Widget { private ariaLabel: string; private validation?: IInputValidator; private state: 'idle' | 'open' | 'closed' = 'idle'; - private cachedHeight: number | null; + + private mirror: HTMLElement | undefined; + private cachedHeight: number | undefined; + private cachedContentHeight: number | undefined; + private maxHeight: number = Number.POSITIVE_INFINITY; + private scrollableElement: ScrollableElement | undefined; // {{SQL CARBON EDIT}} - Add showValidationMessage and set inputBackground, inputForeground, and inputBorder as protected protected showValidationMessage: boolean; @@ -133,7 +141,6 @@ export class InputBox extends Widget { this.options = options || Object.create(null); mixin(this.options, defaultOpts, false); this.message = null; - this.cachedHeight = null; this.placeholder = this.options.placeholder || ''; this.ariaLabel = this.options.ariaLabel || ''; @@ -171,8 +178,26 @@ export class InputBox extends Widget { this.onblur(this.input, () => dom.removeClass(this.element, 'synthetic-focus')); if (this.options.flexibleHeight) { + this.maxHeight = typeof this.options.flexibleMaxHeight === 'number' ? this.options.flexibleMaxHeight : Number.POSITIVE_INFINITY; + this.mirror = dom.append(wrapper, $('div.mirror')); this.mirror.innerHTML = ' '; + + this.scrollableElement = new ScrollableElement(this.element, { vertical: ScrollbarVisibility.Auto }); + dom.append(container, this.scrollableElement.getDomNode()); + this._register(this.scrollableElement); + + // from ScrollableElement to DOM + this._register(this.scrollableElement.onScroll(e => this.input.scrollTop = e.scrollTop)); + + const onSelectionChange = Event.filter(domEvent(document, 'selectionchange'), () => { + const selection = document.getSelection(); + return !!selection && selection.anchorNode === wrapper; + }); + + // from DOM to ScrollableElement + this._register(onSelectionChange(this.updateScrollDimensions, this)); + this._register(this.onDidHeightChange(this.updateScrollDimensions, this)); } else { this.input.type = this.options.type || 'text'; this.input.setAttribute('wrap', 'off'); @@ -254,7 +279,7 @@ export class InputBox extends Widget { } } - public get mirrorElement(): HTMLElement { + public get mirrorElement(): HTMLElement | undefined { return this.mirror; } @@ -274,7 +299,7 @@ export class InputBox extends Widget { } public get height(): number { - return this.cachedHeight === null ? dom.getTotalHeight(this.element) : this.cachedHeight; + return typeof this.cachedHeight === 'number' ? this.cachedHeight : dom.getTotalHeight(this.element); } public focus(): void { @@ -325,6 +350,19 @@ export class InputBox extends Widget { } } + private updateScrollDimensions(): void { + if (typeof this.cachedContentHeight !== 'number' || typeof this.cachedHeight !== 'number') { + return; + } + + const scrollHeight = this.cachedContentHeight; + const height = this.cachedHeight; + const scrollTop = this.input.scrollTop; + + this.scrollableElement!.setScrollDimensions({ scrollHeight, height }); + this.scrollableElement!.setScrollPosition({ scrollTop }); + } + public showMessage(message: IMessage, force?: boolean): void { this.message = message; @@ -544,12 +582,13 @@ export class InputBox extends Widget { return; } - const previousHeight = this.cachedHeight; - this.cachedHeight = dom.getTotalHeight(this.mirror); + const previousHeight = this.cachedContentHeight; + this.cachedContentHeight = dom.getTotalHeight(this.mirror); - if (previousHeight !== this.cachedHeight) { + if (previousHeight !== this.cachedContentHeight) { + this.cachedHeight = Math.min(this.cachedContentHeight, this.maxHeight); this.input.style.height = this.cachedHeight + 'px'; - this._onDidHeightChange.fire(this.cachedHeight); + this._onDidHeightChange.fire(this.cachedContentHeight); } } diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index b12dce215c..c8343521d9 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -73,7 +73,7 @@ class PagedRenderer implements IListRenderer implements IDisposable { private list: List; - private _model: IPagedModel; + private _model!: IPagedModel; constructor( container: HTMLElement, diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 8a23e82a54..a73f94d79d 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -57,6 +57,7 @@ export interface IListViewOptions { readonly mouseSupport?: boolean; readonly horizontalScrolling?: boolean; readonly ariaProvider?: IAriaProvider; + readonly additionalScrollHeight?: number; } const DefaultOptions = { @@ -163,19 +164,19 @@ export class ListView implements ISpliceable, IDisposable { private lastRenderTop: number; private lastRenderHeight: number; private renderWidth = 0; - private gesture: Gesture; private rowsContainer: HTMLElement; private scrollableElement: ScrollableElement; - private _scrollHeight: number; + private _scrollHeight: number = 0; private scrollableElementUpdateDisposable: IDisposable | null = null; private scrollableElementWidthDelayer = new Delayer(50); private splicing = false; private dragOverAnimationDisposable: IDisposable | undefined; private dragOverAnimationStopDisposable: IDisposable = Disposable.None; - private dragOverMouseY: number; + private dragOverMouseY: number = 0; private setRowLineHeight: boolean; private supportDynamicHeights: boolean; private horizontalScrolling: boolean; + private additionalScrollHeight: number; private ariaProvider: IAriaProvider; private scrollWidth: number | undefined; private canUseTranslate3d: boolean | undefined = undefined; @@ -229,6 +230,8 @@ export class ListView implements ISpliceable, IDisposable { this.horizontalScrolling = getOrDefault(options, o => o.horizontalScrolling, DefaultOptions.horizontalScrolling); DOM.toggleClass(this.domNode, 'horizontal-scrolling', this.horizontalScrolling); + this.additionalScrollHeight = typeof options.additionalScrollHeight === 'undefined' ? 0 : options.additionalScrollHeight; + this.ariaProvider = options.ariaProvider || { getSetSize: (e, i, length) => length, getPosInSet: (_, index) => index + 1 }; this.rowsContainer = document.createElement('div'); @@ -245,7 +248,7 @@ export class ListView implements ISpliceable, IDisposable { this.domNode.appendChild(this.scrollableElement.getDomNode()); container.appendChild(this.domNode); - this.disposables = [this.rangeMap, this.gesture, this.scrollableElement, this.cache]; + this.disposables = [this.rangeMap, this.scrollableElement, this.cache]; this.scrollableElement.onScroll(this.onScroll, this, this.disposables); domEvent(this.rowsContainer, TouchEventType.Change)(this.onTouchChange, this, this.disposables); @@ -689,7 +692,7 @@ export class ListView implements ISpliceable, IDisposable { } get scrollHeight(): number { - return this._scrollHeight + (this.horizontalScrolling ? 10 : 0); + return this._scrollHeight + (this.horizontalScrolling ? 10 : 0) + this.additionalScrollHeight; } // Events diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index d7a26fc88f..f99e79b186 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -519,7 +519,7 @@ const DefaultOpenController: IOpenController = { export class MouseController implements IDisposable { private multipleSelectionSupport: boolean; - readonly multipleSelectionController: IMultipleSelectionController; + readonly multipleSelectionController: IMultipleSelectionController | undefined; private openController: IOpenController; private mouseSupport: boolean; private readonly disposables = new DisposableStore(); @@ -618,7 +618,7 @@ export class MouseController implements IDisposable { } } - private onDoubleClick(e: IListMouseEvent): void { + protected onDoubleClick(e: IListMouseEvent): void { if (isInputElement(e.browserEvent.target as HTMLElement)) { return; } diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 2ca7b6de23..e1300c08ac 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -20,22 +20,8 @@ import { Event, Emitter } from 'vs/base/common/event'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { isLinux, isMacintosh } from 'vs/base/common/platform'; -function createMenuMnemonicRegExp() { - try { - return new RegExp('\\(&([^\\s&])\\)|(? { + if (!this.element) { + return; + } + + this._register(addDisposableListener(this.element, EventType.MOUSE_UP, e => { + EventHelper.stop(e, true); + this.onClick(e); + })); + }, 50); + + this._register(this.runOnceToEnableMouseUp); } render(container: HTMLElement): void { @@ -425,10 +426,8 @@ class BaseMenuActionViewItem extends BaseActionViewItem { append(this.item, $('span.keybinding')).textContent = this.options.keybinding; } - this._register(addDisposableListener(this.element, EventType.MOUSE_UP, e => { - EventHelper.stop(e, true); - this.onClick(e); - })); + // Adds mouse up listener to actually run the action + this.runOnceToEnableMouseUp.schedule(); this.updateClass(); this.updateLabel(); @@ -467,9 +466,23 @@ class BaseMenuActionViewItem extends BaseActionViewItem { const matches = MENU_MNEMONIC_REGEX.exec(label); if (matches) { - label = strings.escape(label).replace(MENU_ESCAPED_MNEMONIC_REGEX, ''); + label = strings.escape(label); + + // This is global, reset it + MENU_ESCAPED_MNEMONIC_REGEX.lastIndex = 0; + let escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(label); + + // We can't use negative lookbehind so if we match our negative and skip + while (escMatch && escMatch[1]) { + escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(label); + } + + if (escMatch) { + label = `${label.substr(0, escMatch.index)}${label.substr(escMatch.index + escMatch[0].length)}`; + } + label = label.replace(/&&/g, '&'); - this.item.setAttribute('aria-keyshortcuts', (!!matches[1] ? matches[1] : matches[2]).toLocaleLowerCase()); + this.item.setAttribute('aria-keyshortcuts', (!!matches[1] ? matches[1] : matches[3]).toLocaleLowerCase()); } else { label = label.replace(/&&/g, '&'); } @@ -802,7 +815,7 @@ export function cleanMnemonic(label: string): string { return label; } - const mnemonicInText = matches[0].charAt(0) === '&'; + const mnemonicInText = !matches[1]; - return label.replace(regex, mnemonicInText ? '$2' : '').trim(); + return label.replace(regex, mnemonicInText ? '$2$3' : '').trim(); } diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index af6b70784f..3860b17388 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -209,7 +209,7 @@ export class MenuBar extends Disposable { // Register mnemonics if (mnemonicMatches) { - let mnemonic = !!mnemonicMatches[1] ? mnemonicMatches[1] : mnemonicMatches[2]; + let mnemonic = !!mnemonicMatches[1] ? mnemonicMatches[1] : mnemonicMatches[3]; this.registerMnemonic(this.menuCache.length, mnemonic); } @@ -472,15 +472,34 @@ export class MenuBar extends Disposable { const cleanMenuLabel = cleanMnemonic(label); // Update the button label to reflect mnemonics - titleElement.innerHTML = this.options.enableMnemonics ? - strings.escape(label).replace(MENU_ESCAPED_MNEMONIC_REGEX, '').replace(/&&/g, '&') : - cleanMenuLabel.replace(/&&/g, '&'); + + if (this.options.enableMnemonics) { + let innerHtml = strings.escape(label); + + // This is global so reset it + MENU_ESCAPED_MNEMONIC_REGEX.lastIndex = 0; + let escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(innerHtml); + + // We can't use negative lookbehind so we match our negative and skip + while (escMatch && escMatch[1]) { + escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(innerHtml); + } + + if (escMatch) { + innerHtml = `${innerHtml.substr(0, escMatch.index)}${innerHtml.substr(escMatch.index + escMatch[0].length)}`; + } + + innerHtml = innerHtml.replace(/&&/g, '&'); + titleElement.innerHTML = innerHtml; + } else { + titleElement.innerHTML = cleanMenuLabel.replace(/&&/g, '&'); + } let mnemonicMatches = MENU_MNEMONIC_REGEX.exec(label); // Register mnemonics if (mnemonicMatches) { - let mnemonic = !!mnemonicMatches[1] ? mnemonicMatches[1] : mnemonicMatches[2]; + let mnemonic = !!mnemonicMatches[1] ? mnemonicMatches[1] : mnemonicMatches[3]; if (this.options.enableMnemonics) { buttonElement.setAttribute('aria-keyshortcuts', 'Alt+' + mnemonic.toLocaleLowerCase()); diff --git a/src/vs/base/browser/ui/sash/sash.ts b/src/vs/base/browser/ui/sash/sash.ts index a69252d55b..ae0c57058d 100644 --- a/src/vs/base/browser/ui/sash/sash.ts +++ b/src/vs/base/browser/ui/sash/sash.ts @@ -61,7 +61,7 @@ export class Sash extends Disposable { private el: HTMLElement; private layoutProvider: ISashLayoutProvider; private hidden: boolean; - private orientation: Orientation; + private orientation!: Orientation; private _state: SashState = SashState.Enabled; get state(): SashState { return this._state; } diff --git a/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts b/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts index 10c3d6dc1a..e50958988b 100644 --- a/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts +++ b/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts @@ -49,7 +49,7 @@ export abstract class AbstractScrollbar extends Widget { private _mouseMoveMonitor: GlobalMouseMoveMonitor; public domNode: FastDomNode; - public slider: FastDomNode; + public slider!: FastDomNode; protected _shouldRender: boolean; diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index dec154597c..7f3b977818 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -148,9 +148,9 @@ export abstract class AbstractScrollableElement extends Widget { private readonly _horizontalScrollbar: HorizontalScrollbar; private readonly _domNode: HTMLElement; - private readonly _leftShadowDomNode: FastDomNode; - private readonly _topShadowDomNode: FastDomNode; - private readonly _topLeftShadowDomNode: FastDomNode; + private readonly _leftShadowDomNode: FastDomNode | null; + private readonly _topShadowDomNode: FastDomNode | null; + private readonly _topLeftShadowDomNode: FastDomNode | null; private readonly _listenOnDomNode: HTMLElement; @@ -207,6 +207,10 @@ export abstract class AbstractScrollableElement extends Widget { this._topLeftShadowDomNode = createFastDomNode(document.createElement('div')); this._topLeftShadowDomNode.setClassName('shadow top-left-corner'); this._domNode.appendChild(this._topLeftShadowDomNode.domNode); + } else { + this._leftShadowDomNode = null; + this._topShadowDomNode = null; + this._topLeftShadowDomNode = null; } this._listenOnDomNode = this._options.listenOnDomNode || this._domNode; @@ -430,9 +434,9 @@ export abstract class AbstractScrollableElement extends Widget { let enableTop = scrollState.scrollTop > 0; let enableLeft = scrollState.scrollLeft > 0; - this._leftShadowDomNode.setClassName('shadow' + (enableLeft ? ' left' : '')); - this._topShadowDomNode.setClassName('shadow' + (enableTop ? ' top' : '')); - this._topLeftShadowDomNode.setClassName('shadow top-left-corner' + (enableTop ? ' top' : '') + (enableLeft ? ' left' : '')); + this._leftShadowDomNode!.setClassName('shadow' + (enableLeft ? ' left' : '')); + this._topShadowDomNode!.setClassName('shadow' + (enableTop ? ' top' : '')); + this._topLeftShadowDomNode!.setClassName('shadow top-left-corner' + (enableTop ? ' top' : '') + (enableLeft ? ' left' : '')); } } diff --git a/src/vs/base/browser/ui/selectBox/selectBox.ts b/src/vs/base/browser/ui/selectBox/selectBox.ts index 2ce97a42ce..bba7797587 100644 --- a/src/vs/base/browser/ui/selectBox/selectBox.ts +++ b/src/vs/base/browser/ui/selectBox/selectBox.ts @@ -8,7 +8,7 @@ import 'vs/css!./selectBox'; import { Event } from 'vs/base/common/event'; import { Widget } from 'vs/base/browser/ui/widget'; import { Color } from 'vs/base/common/color'; -import { deepClone, mixin } from 'vs/base/common/objects'; +import { deepClone } from 'vs/base/common/objects'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; import { SelectBoxNative } from 'vs/base/browser/ui/selectBox/selectBoxNative'; @@ -77,14 +77,11 @@ export class SelectBox extends Widget implements ISelectBoxDelegate { protected selectBackground?: Color; protected selectForeground?: Color; protected selectBorder?: Color; - private styles: ISelectBoxStyles; private selectBoxDelegate: ISelectBoxDelegate; constructor(options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, styles: ISelectBoxStyles = deepClone(defaultStyles), selectBoxOptions?: ISelectBoxOptions) { super(); - mixin(this.styles, defaultStyles, false); - // Default to native SelectBox for OSX unless overridden if (isMacintosh && !(selectBoxOptions && selectBoxOptions.useCustomDrawn)) { this.selectBoxDelegate = new SelectBoxNative(options, selected, styles, selectBoxOptions); diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index 0c80f04675..ae7f5aa370 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -93,23 +93,24 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi private _isVisible: boolean; private selectBoxOptions: ISelectBoxOptions; + // {{SQL CARBON EDIT}} public selectElement: HTMLSelectElement; - private options: ISelectOptionItem[]; + private options: ISelectOptionItem[] = []; private selected: number; private readonly _onDidSelect: Emitter; private styles: ISelectBoxStyles; - private listRenderer: SelectListRenderer; - private contextViewProvider: IContextViewProvider; - private selectDropDownContainer: HTMLElement; - private styleElement: HTMLStyleElement; - private selectList: List; - private selectDropDownListContainer: HTMLElement; - private widthControlElement: HTMLElement; - private _currentSelection: number; - private _dropDownPosition: AnchorPosition; + private listRenderer!: SelectListRenderer; + private contextViewProvider!: IContextViewProvider; + private selectDropDownContainer!: HTMLElement; + private styleElement!: HTMLStyleElement; + private selectList!: List; + private selectDropDownListContainer!: HTMLElement; + private widthControlElement!: HTMLElement; + private _currentSelection = 0; + private _dropDownPosition!: AnchorPosition; private _hasDetails: boolean = false; - private selectionDetailsPane: HTMLElement; + private selectionDetailsPane!: HTMLElement; private _skipLayout: boolean = false; private _sticky: boolean = false; // for dev purposes only @@ -247,7 +248,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi } public setOptions(options: ISelectOptionItem[], selected?: number): void { - if (!this.options || !arrays.equals(this.options, options)) { + if (!arrays.equals(this.options, options)) { this.options = options; this.selectElement.options.length = 0; this._hasDetails = false; @@ -272,7 +273,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi // Mirror options in drop-down // Populate select list for non-native select mode - if (this.selectList && !!this.options) { + if (this.selectList) { this.selectList.splice(0, this.selectList.length, this.options); } } @@ -704,7 +705,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi private setWidthControlElement(container: HTMLElement): number { let elementWidth = 0; - if (container && !!this.options) { + if (container) { let longest = 0; let longestLength = 0; diff --git a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts index ad588bb356..0d740f729e 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts @@ -17,7 +17,7 @@ export class SelectBoxNative extends Disposable implements ISelectBoxDelegate { public selectElement: HTMLSelectElement; private selectBoxOptions: ISelectBoxOptions; private options: ISelectOptionItem[]; - private selected: number; + private selected = 0; private readonly _onDidSelect: Emitter; private styles: ISelectBoxStyles; diff --git a/src/vs/base/browser/ui/splitview/panelview.ts b/src/vs/base/browser/ui/splitview/panelview.ts index 621eaff0f0..fad62d8895 100644 --- a/src/vs/base/browser/ui/splitview/panelview.ts +++ b/src/vs/base/browser/ui/splitview/panelview.ts @@ -42,8 +42,8 @@ export abstract class Panel extends Disposable implements IView { private static readonly HEADER_SIZE = 22; readonly element: HTMLElement; - private header: HTMLElement; - private body: HTMLElement; + private header!: HTMLElement; + private body!: HTMLElement; protected _expanded: boolean; @@ -109,7 +109,7 @@ export abstract class Panel extends Disposable implements IView { return headerSize + maximumBodySize; } - width: number; + width: number = 0; constructor(options: IPanelOptions = {}) { super(); @@ -371,7 +371,7 @@ export class PanelView extends Disposable { private dndContext: IDndContext = { draggable: null }; private el: HTMLElement; private panelItems: IPanelItem[] = []; - private width: number; + private width: number = 0; private splitview: SplitView; private animationTimer: number | undefined = undefined; diff --git a/src/vs/base/browser/ui/splitview/splitview.css b/src/vs/base/browser/ui/splitview/splitview.css index f9882a893a..1a43420662 100644 --- a/src/vs/base/browser/ui/splitview/splitview.css +++ b/src/vs/base/browser/ui/splitview/splitview.css @@ -72,4 +72,4 @@ .monaco-split-view2.separator-border.vertical > .split-view-container > .split-view-view:not(:first-child)::before { height: 1px; width: 100%; -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index bdd519dc66..b541d32941 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -79,7 +79,7 @@ abstract class ViewItem { return typeof this._cachedVisibleSize === 'undefined'; } - set visible(visible: boolean) { + setVisible(visible: boolean, size?: number): void { if (visible === this.visible) { return; } @@ -88,7 +88,7 @@ abstract class ViewItem { this.size = clamp(this._cachedVisibleSize!, this.viewMinimumSize, this.viewMaximumSize); this._cachedVisibleSize = undefined; } else { - this._cachedVisibleSize = this.size; + this._cachedVisibleSize = typeof size === 'number' ? size : this.size; this.size = 0; } @@ -125,7 +125,10 @@ abstract class ViewItem { dom.addClass(container, 'visible'); } - abstract layout(): void; + layout(): void { + this.container.scrollTop = 0; + this.container.scrollLeft = 0; + } layoutView(orientation: Orientation): void { this.view.layout(this.size, orientation); @@ -140,6 +143,7 @@ abstract class ViewItem { class VerticalViewItem extends ViewItem { layout(): void { + super.layout(); this.container.style.height = `${this.size}px`; this.layoutView(Orientation.VERTICAL); } @@ -148,6 +152,7 @@ class VerticalViewItem extends ViewItem { class HorizontalViewItem extends ViewItem { layout(): void { + super.layout(); this.container.style.width = `${this.size}px`; this.layoutView(Orientation.HORIZONTAL); } @@ -161,6 +166,7 @@ interface ISashItem { interface ISashDragSnapState { readonly index: number; readonly limitDelta: number; + readonly size: number; } interface ISashDragState { @@ -203,7 +209,7 @@ export class SplitView extends Disposable { private proportions: undefined | number[] = undefined; private viewItems: ViewItem[] = []; private sashItems: ISashItem[] = []; - private sashDragState: ISashDragState; + private sashDragState: ISashDragState | undefined; private state: State = State.Idle; private inverseAltBehavior: boolean; private proportionalLayout: boolean; @@ -414,9 +420,10 @@ export class SplitView extends Disposable { throw new Error('Cant modify splitview'); } - const size = this.getViewSize(from); + const cachedVisibleSize = this.getViewCachedVisibleSize(from); + const sizing = typeof cachedVisibleSize === 'undefined' ? this.getViewSize(from) : Sizing.Invisible(cachedVisibleSize); const view = this.removeView(from); - this.addView(view, size, to); + this.addView(view, sizing, to); } swapViews(from: number, to: number): void { @@ -452,10 +459,11 @@ export class SplitView extends Disposable { } const viewItem = this.viewItems[index]; - viewItem.visible = visible; + viewItem.setVisible(visible); this.distributeEmptySpace(index); this.layoutViews(); + this.saveProportions(); } getViewCachedVisibleSize(index: number): number | undefined { @@ -499,8 +507,8 @@ export class SplitView extends Disposable { // This way, we can press Alt while we resize a sash, macOS style! const disposable = combinedDisposable( - domEvent(document.body, 'keydown')(e => resetSashDragState(this.sashDragState.current, e.altKey)), - domEvent(document.body, 'keyup')(() => resetSashDragState(this.sashDragState.current, false)) + domEvent(document.body, 'keydown')(e => resetSashDragState(this.sashDragState!.current, e.altKey)), + domEvent(document.body, 'keyup')(() => resetSashDragState(this.sashDragState!.current, false)) ); const resetSashDragState = (start: number, alt: boolean) => { @@ -550,7 +558,8 @@ export class SplitView extends Disposable { snapBefore = { index: snapBeforeIndex, - limitDelta: viewItem.visible ? minDelta - halfSize : minDelta + halfSize + limitDelta: viewItem.visible ? minDelta - halfSize : minDelta + halfSize, + size: viewItem.size }; } @@ -560,7 +569,8 @@ export class SplitView extends Disposable { snapAfter = { index: snapAfterIndex, - limitDelta: viewItem.visible ? maxDelta + halfSize : maxDelta - halfSize + limitDelta: viewItem.visible ? maxDelta + halfSize : maxDelta - halfSize, + size: viewItem.size }; } } @@ -572,8 +582,8 @@ export class SplitView extends Disposable { } private onSashChange({ current }: ISashEvent): void { - const { index, start, sizes, alt, minDelta, maxDelta, snapBefore, snapAfter } = this.sashDragState; - this.sashDragState.current = current; + const { index, start, sizes, alt, minDelta, maxDelta, snapBefore, snapAfter } = this.sashDragState!; + this.sashDragState!.current = current; const delta = current - start; const newDelta = this.resize(index, delta, sizes, undefined, undefined, minDelta, maxDelta, snapBefore, snapAfter); @@ -596,7 +606,7 @@ export class SplitView extends Disposable { private onSashEnd(index: number): void { this._onDidSashChange.fire(index); - this.sashDragState.disposable.dispose(); + this.sashDragState!.disposable.dispose(); this.saveProportions(); } @@ -738,14 +748,14 @@ export class SplitView extends Disposable { const snapView = this.viewItems[snapBefore.index]; const visible = delta >= snapBefore.limitDelta; snapped = visible !== snapView.visible; - snapView.visible = visible; + snapView.setVisible(visible, snapBefore.size); } if (!snapped && snapAfter) { const snapView = this.viewItems[snapAfter.index]; const visible = delta < snapAfter.limitDelta; snapped = visible !== snapView.visible; - snapView.visible = visible; + snapView.setVisible(visible, snapAfter.size); } if (snapped) { diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index 1e676d6d37..9ab6c3a3ad 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -33,7 +33,7 @@ export class ToolBar extends Disposable { private actionBar: ActionBar; private toggleMenuAction: ToggleMenuAction; private toggleMenuActionViewItem = this._register(new MutableDisposable()); - private hasSecondaryActions: boolean; + private hasSecondaryActions: boolean = false; private lookupKeybindings: boolean; constructor(container: HTMLElement, contextMenuProvider: IContextMenuProvider, options: IToolBarOptions = { orientation: ActionsOrientation.HORIZONTAL }) { @@ -162,6 +162,7 @@ class ToggleMenuAction extends Action { title = title || nls.localize('moreActions', "More Actions..."); super(ToggleMenuAction.ID, title, undefined, true); + this._menuActions = []; this.toggleDropdownMenu = toggleDropdownMenu; } @@ -178,4 +179,4 @@ class ToggleMenuAction extends Action { set menuActions(actions: ReadonlyArray) { this._menuActions = actions; } -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 38ab665b13..87328f2f2b 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -451,8 +451,8 @@ class TypeFilter implements ITreeFilter, IDisposable { private _matchCount = 0; get matchCount(): number { return this._matchCount; } - private _pattern: string; - private _lowercasePattern: string; + private _pattern: string = ''; + private _lowercasePattern: string = ''; private disposables: IDisposable[] = []; set pattern(pattern: string) { @@ -543,7 +543,7 @@ class TypeFilterController implements IDisposable { private _filterOnType: boolean; get filterOnType(): boolean { return this._filterOnType; } - private _empty: boolean; + private _empty: boolean = false; get empty(): boolean { return this._empty; } private _onDidChangeEmptyState = new Emitter(); @@ -897,6 +897,7 @@ export interface IAbstractTreeOptions extends IAbstractTr readonly autoExpandSingleChildren?: boolean; readonly keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter; readonly expandOnlyOnTwistieClick?: boolean | ((e: T) => boolean); + readonly additionalScrollHeight?: number; } function dfs(node: ITreeNode, fn: (node: ITreeNode) => void): void { @@ -1063,6 +1064,16 @@ class TreeNodeListMouseController extends MouseController< super.onPointer(e); } + + protected onDoubleClick(e: IListMouseEvent>): void { + const onTwistie = hasClass(e.browserEvent.target as HTMLElement, 'monaco-tl-twistie'); + + if (onTwistie) { + return; + } + + super.onDoubleClick(e); + } } interface ITreeNodeListOptions extends IListOptions> { diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index ce6b4122a9..958bb3d60a 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -238,7 +238,8 @@ function asObjectTreeOptions(options?: IAsyncDataTreeOpt e => (options.expandOnlyOnTwistieClick as ((e: T) => boolean))(e.element as T) ) ), - ariaProvider: undefined + ariaProvider: undefined, + additionalScrollHeight: options.additionalScrollHeight }; } diff --git a/src/vs/base/browser/ui/tree/compressedObjectTree.ts b/src/vs/base/browser/ui/tree/compressedObjectTree.ts index c24f666e9d..9e5f2c9c9a 100644 --- a/src/vs/base/browser/ui/tree/compressedObjectTree.ts +++ b/src/vs/base/browser/ui/tree/compressedObjectTree.ts @@ -17,7 +17,7 @@ export interface IObjectTreeOptions extends IAbstractTree export class CompressedObjectTree, TFilterData = void> extends AbstractTree | null, TFilterData, T | null> { - protected model: CompressedTreeModel; + protected model!: CompressedTreeModel; get onDidChangeCollapseState(): Event | null, TFilterData>> { return this.model.onDidChangeCollapseState; } diff --git a/src/vs/base/browser/ui/tree/dataTree.ts b/src/vs/base/browser/ui/tree/dataTree.ts index 4e5cce69d2..ffded10a45 100644 --- a/src/vs/base/browser/ui/tree/dataTree.ts +++ b/src/vs/base/browser/ui/tree/dataTree.ts @@ -23,7 +23,7 @@ export interface IDataTreeViewState { export class DataTree extends AbstractTree { - protected model: ObjectTreeModel; + protected model!: ObjectTreeModel; private input: TInput | undefined; private identityProvider: IIdentityProvider | undefined; diff --git a/src/vs/base/browser/ui/tree/indexTree.ts b/src/vs/base/browser/ui/tree/indexTree.ts index b86a170b38..b709f66b29 100644 --- a/src/vs/base/browser/ui/tree/indexTree.ts +++ b/src/vs/base/browser/ui/tree/indexTree.ts @@ -15,7 +15,7 @@ export interface IIndexTreeOptions extends IAbstractTreeO export class IndexTree extends AbstractTree { - protected model: IndexTreeModel; + protected model!: IndexTreeModel; constructor( container: HTMLElement, diff --git a/src/vs/base/browser/ui/tree/objectTree.ts b/src/vs/base/browser/ui/tree/objectTree.ts index 04327496ab..b5b40425dc 100644 --- a/src/vs/base/browser/ui/tree/objectTree.ts +++ b/src/vs/base/browser/ui/tree/objectTree.ts @@ -17,7 +17,7 @@ export interface IObjectTreeOptions extends IAbstractTree export class ObjectTree, TFilterData = void> extends AbstractTree { - protected model: IObjectTreeModel; + protected model!: IObjectTreeModel; get onDidChangeCollapseState(): Event> { return this.model.onDidChangeCollapseState; } diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index 0621c5ca1f..ac03c7b9b7 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -64,11 +64,11 @@ export class Action extends Disposable implements IAction { protected readonly _id: string; protected _label: string; - protected _tooltip: string; + protected _tooltip: string | undefined; protected _cssClass: string | undefined; - protected _enabled: boolean; - protected _checked: boolean; - protected _radio: boolean; + protected _enabled: boolean = true; + protected _checked: boolean = false; + protected _radio: boolean = false; protected readonly _actionCallback?: (event?: any) => Promise; constructor(id: string, label: string = '', cssClass: string = '', enabled: boolean = true, actionCallback?: (event?: any) => Promise) { @@ -100,7 +100,7 @@ export class Action extends Disposable implements IAction { } get tooltip(): string { - return this._tooltip; + return this._tooltip || ''; } set tooltip(value: string) { diff --git a/src/vs/base/common/color.ts b/src/vs/base/common/color.ts index 4013db5d53..0894cc929f 100644 --- a/src/vs/base/common/color.ts +++ b/src/vs/base/common/color.ts @@ -293,7 +293,7 @@ export class Color { } } - equals(other: Color): boolean { + equals(other: Color | null): boolean { return !!other && RGBA.equals(this.rgba, other.rgba) && HSLA.equals(this.hsla, other.hsla) && HSVA.equals(this.hsva, other.hsva); } @@ -608,4 +608,4 @@ export namespace Color { } } } -} \ No newline at end of file +} diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 4e9c31737c..4d81a04a24 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -645,7 +645,7 @@ export interface IWaitUntil { export class AsyncEmitter extends Emitter { - private _asyncDeliveryQueue: [Listener, T, Promise[]][]; + private _asyncDeliveryQueue?: [Listener, T, Promise[]][]; async fireAsync(eventFn: (thenables: Promise[], listener: Function) => T): Promise { if (!this._listeners) { diff --git a/src/vs/base/common/history.ts b/src/vs/base/common/history.ts index f0798acd51..8172fd8cd1 100644 --- a/src/vs/base/common/history.ts +++ b/src/vs/base/common/history.ts @@ -7,9 +7,9 @@ import { INavigator, ArrayNavigator } from 'vs/base/common/iterator'; export class HistoryNavigator implements INavigator { - private _history: Set; + private _history!: Set; private _limit: number; - private _navigator: ArrayNavigator; + private _navigator!: ArrayNavigator; constructor(history: T[] = [], limit: number = 10) { this._initialize(history); diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index a15da16937..81150addc0 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -112,9 +112,9 @@ export class StringIterator implements IKeyIterator { export class PathIterator implements IKeyIterator { - private _value: string; - private _from: number; - private _to: number; + private _value!: string; + private _from!: number; + private _to!: number; reset(key: string): this { this._value = key.replace(/\\$|\/$/, ''); @@ -176,9 +176,9 @@ export class PathIterator implements IKeyIterator { } class TernarySearchTreeNode { - segment: string; + segment!: string; value: E | undefined; - key: string; + key!: string; left: TernarySearchTreeNode | undefined; mid: TernarySearchTreeNode | undefined; right: TernarySearchTreeNode | undefined; diff --git a/src/vs/base/common/path.ts b/src/vs/base/common/path.ts index 09ef68cdbb..5a0d15f8cd 100644 --- a/src/vs/base/common/path.ts +++ b/src/vs/base/common/path.ts @@ -58,6 +58,8 @@ class ErrorInvalidArgType extends Error { msg += `. Received type ${typeof actual}`; super(msg); + + this.code = 'ERR_INVALID_ARG_TYPE'; } } diff --git a/src/vs/base/node/encoding.ts b/src/vs/base/node/encoding.ts index 46199670df..6e0e8dbb3b 100644 --- a/src/vs/base/node/encoding.ts +++ b/src/vs/base/node/encoding.ts @@ -14,6 +14,12 @@ export const UTF8_with_bom = 'utf8bom'; export const UTF16be = 'utf16be'; export const UTF16le = 'utf16le'; +export type UTF_ENCODING = typeof UTF8 | typeof UTF8_with_bom | typeof UTF16be | typeof UTF16le; + +export function isUTFEncoding(encoding: string): encoding is UTF_ENCODING { + return [UTF8, UTF8_with_bom, UTF16be, UTF16le].some(utfEncoding => utfEncoding === encoding); +} + export const UTF16be_BOM = [0xFE, 0xFF]; export const UTF16le_BOM = [0xFF, 0xFE]; export const UTF8_BOM = [0xEF, 0xBB, 0xBF]; @@ -41,8 +47,8 @@ export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions return new Promise((resolve, reject) => { const writer = new class extends Writable { - private decodeStream: NodeJS.ReadWriteStream; - private decodeStreamPromise: Promise; + private decodeStream: NodeJS.ReadWriteStream | undefined; + private decodeStreamPromise: Promise | undefined; private bufferedChunks: Buffer[] = []; private bytesBuffered = 0; @@ -116,7 +122,7 @@ export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions // detection. thus, wrap up starting the stream even // without all the data to get things going else { - this._startDecodeStream(() => this.decodeStream.end(callback)); + this._startDecodeStream(() => this.decodeStream!.end(callback)); } } }; diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts index da1737d861..9313a89adb 100644 --- a/src/vs/base/node/pfs.ts +++ b/src/vs/base/node/pfs.ts @@ -138,20 +138,6 @@ export async function readdir(path: string): Promise { return handleDirectoryChildren(await promisify(fs.readdir)(path)); } -export async function readdirWithFileTypes(path: string): Promise { - const children = await promisify(fs.readdir)(path, { withFileTypes: true }); - - // Mac: uses NFD unicode form on disk, but we want NFC - // See also https://github.com/nodejs/node/issues/2165 - if (platform.isMacintosh) { - for (const child of children) { - child.name = normalizeNFC(child.name); - } - } - - return children; -} - export function readdirSync(path: string): string[] { return handleDirectoryChildren(fs.readdirSync(path)); } diff --git a/src/vs/base/node/stream.ts b/src/vs/base/node/stream.ts index 7a85484fd4..3693165c30 100644 --- a/src/vs/base/node/stream.ts +++ b/src/vs/base/node/stream.ts @@ -4,6 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as fs from 'fs'; +import { VSBufferReadableStream, VSBufferReadable, VSBuffer } from 'vs/base/common/buffer'; +import { Readable } from 'stream'; +import { isUndefinedOrNull } from 'vs/base/common/types'; +import { UTF8, UTF8_with_bom, UTF8_BOM, UTF16be, UTF16le_BOM, UTF16be_BOM, UTF16le, UTF_ENCODING } from 'vs/base/node/encoding'; /** * Reads a file until a matching string is found. @@ -66,4 +70,107 @@ export function readToMatchingString(file: string, matchingString: string, chunk readChunk(); }) ); -} \ No newline at end of file +} + +export function streamToNodeReadable(stream: VSBufferReadableStream): Readable { + return new class extends Readable { + private listening = false; + + _read(size?: number): void { + if (!this.listening) { + this.listening = true; + + // Data + stream.on('data', data => { + try { + if (!this.push(data.buffer)) { + stream.pause(); // pause the stream if we should not push anymore + } + } catch (error) { + this.emit(error); + } + }); + + // End + stream.on('end', () => { + try { + this.push(null); // signal EOS + } catch (error) { + this.emit(error); + } + }); + + // Error + stream.on('error', error => this.emit('error', error)); + } + + // ensure the stream is flowing + stream.resume(); + } + + _destroy(error: Error | null, callback: (error: Error | null) => void): void { + stream.destroy(); + + callback(null); + } + }; +} + +export function nodeReadableToString(stream: NodeJS.ReadableStream): Promise { + return new Promise((resolve, reject) => { + let result = ''; + + stream.on('data', chunk => result += chunk); + stream.on('error', reject); + stream.on('end', () => resolve(result)); + }); +} + +export function nodeStreamToVSBufferReadable(stream: NodeJS.ReadWriteStream, addBOM?: { encoding: UTF_ENCODING }): VSBufferReadable { + let bytesRead = 0; + let done = false; + + return { + read(): VSBuffer | null { + if (done) { + return null; + } + + const res = stream.read(); + if (isUndefinedOrNull(res)) { + done = true; + + // If we are instructed to add a BOM but we detect that no + // bytes have been read, we must ensure to return the BOM + // ourselves so that we comply with the contract. + if (bytesRead === 0 && addBOM) { + switch (addBOM.encoding) { + case UTF8: + case UTF8_with_bom: + return VSBuffer.wrap(Buffer.from(UTF8_BOM)); + case UTF16be: + return VSBuffer.wrap(Buffer.from(UTF16be_BOM)); + case UTF16le: + return VSBuffer.wrap(Buffer.from(UTF16le_BOM)); + } + } + + return null; + } + + // Handle String + if (typeof res === 'string') { + bytesRead += res.length; + + return VSBuffer.fromString(res); + } + + // Handle Buffer + else { + bytesRead += res.byteLength; + + return VSBuffer.wrap(res); + } + } + }; +} diff --git a/src/vs/base/parts/ipc/test/node/ipc.test.ts b/src/vs/base/parts/ipc/test/node/ipc.test.ts index caa949d831..375fb217e6 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.test.ts @@ -31,7 +31,7 @@ class QueueProtocol implements IMessagePassingProtocol { }); readonly onMessage = this._onMessage.event; - other: QueueProtocol; + other!: QueueProtocol; send(buffer: VSBuffer): void { this.other.receive(buffer); diff --git a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts index 03b2da2c2e..60530cfd27 100644 --- a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts +++ b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts @@ -57,7 +57,7 @@ export class QuickOpenEntry { private labelHighlights: IHighlight[]; private descriptionHighlights?: IHighlight[]; private detailHighlights?: IHighlight[]; - private hidden: boolean; + private hidden: boolean | undefined; constructor(highlights: IHighlight[] = []) { this.id = (IDS++).toString(); @@ -148,7 +148,7 @@ export class QuickOpenEntry { * Allows to reuse the same model while filtering. Hidden entries will not show up in the viewer. */ isHidden(): boolean { - return this.hidden; + return !!this.hidden; } /** @@ -352,7 +352,7 @@ class Renderer implements IRenderer { row1.appendChild(icon); // Label - const label = new IconLabel(row1, { supportHighlights: true, supportDescriptionHighlights: true }); + const label = new IconLabel(row1, { supportHighlights: true, supportDescriptionHighlights: true, supportOcticons: true }); // Keybinding const keybindingContainer = document.createElement('span'); diff --git a/src/vs/base/parts/tree/browser/treeModel.ts b/src/vs/base/parts/tree/browser/treeModel.ts index 19f7274239..d547e96951 100644 --- a/src/vs/base/parts/tree/browser/treeModel.ts +++ b/src/vs/base/parts/tree/browser/treeModel.ts @@ -862,10 +862,10 @@ export interface IRefreshEvent extends IBaseEvent { export class TreeModel { private context: _.ITreeContext; - private lock: Lock; + private lock!: Lock; private input: Item | null; - private registry: ItemRegistry; - private registryDisposable: IDisposable; + private registry!: ItemRegistry; + private registryDisposable!: IDisposable; private traitsToItems: ITraitMap; private _onSetInput = new Emitter(); diff --git a/src/vs/base/parts/tree/browser/treeView.ts b/src/vs/base/parts/tree/browser/treeView.ts index 9c86a4c253..a1a34dab35 100644 --- a/src/vs/base/parts/tree/browser/treeView.ts +++ b/src/vs/base/parts/tree/browser/treeView.ts @@ -121,15 +121,15 @@ export class ViewItem implements IViewItem { public top: number; public height: number; public width: number = 0; - public onDragStart: (e: DragEvent) => void; + public onDragStart!: (e: DragEvent) => void; - public needsRender: boolean; - public uri: string | null; + public needsRender: boolean = false; + public uri: string | null = null; public unbindDragStart: Lifecycle.IDisposable = Lifecycle.Disposable.None; public loadingTimer: any; public _styles: any; - private _draggable: boolean; + private _draggable: boolean = false; constructor(context: IViewContext, model: Model.Item) { this.context = context; @@ -174,7 +174,7 @@ export class ViewItem implements IViewItem { return (this.row && this.row.element)!; } - private _templateId: string; + private _templateId: string | undefined; private get templateId(): string { return this._templateId || (this._templateId = (this.context.renderer!.getTemplateId && this.context.renderer!.getTemplateId(this.context.tree, this.model.getElement()))); } @@ -415,8 +415,8 @@ export class TreeView extends HeightMap { private treeStyler: _.ITreeStyler; private rowsContainer: HTMLElement; private scrollableElement: ScrollableElement; - private msGesture: MSGesture; - private lastPointerType: string; + private msGesture: MSGesture | undefined; + private lastPointerType: string = ''; private lastClickTimeStamp: number = 0; private horizontalScrolling: boolean; @@ -425,14 +425,14 @@ export class TreeView extends HeightMap { private lastRenderTop: number; private lastRenderHeight: number; - private inputItem: ViewItem; + private inputItem!: ViewItem; private items: { [id: string]: ViewItem; }; private isRefreshing = false; private refreshingPreviousChildrenIds: { [id: string]: string[] } = {}; private currentDragAndDropData: IDragAndDropData | null = null; private currentDropElement: any; - private currentDropElementReaction: _.IDragOverReaction; + private currentDropElementReaction!: _.IDragOverReaction; private currentDropTarget: ViewItem | null = null; private shouldInvalidateDropReaction: boolean; private currentDropTargets: ViewItem[] | null = null; @@ -443,7 +443,7 @@ export class TreeView extends HeightMap { private didJustPressContextMenuKey: boolean; - private highlightedItemWasDraggable: boolean; + private highlightedItemWasDraggable: boolean = false; private onHiddenScrollTop: number | null = null; private readonly _onDOMFocus = new Emitter(); @@ -633,7 +633,7 @@ export class TreeView extends HeightMap { private setupMSGesture(): void { if ((window).MSGesture) { this.msGesture = new MSGesture(); - setTimeout(() => this.msGesture.target = this.wrapper, 100); // TODO@joh, TODO@IETeam + setTimeout(() => this.msGesture!.target = this.wrapper, 100); // TODO@joh, TODO@IETeam } } diff --git a/src/vs/base/test/common/utils.ts b/src/vs/base/test/common/utils.ts index 950e8750f7..18e864443f 100644 --- a/src/vs/base/test/common/utils.ts +++ b/src/vs/base/test/common/utils.ts @@ -12,8 +12,8 @@ export type ValueCallback = (value: T | Promise) => void; export class DeferredPromise { - private completeCallback: ValueCallback; - private errorCallback: (err: any) => void; + private completeCallback!: ValueCallback; + private errorCallback!: (err: any) => void; public p: Promise; diff --git a/src/vs/base/test/node/pfs/pfs.test.ts b/src/vs/base/test/node/pfs/pfs.test.ts index ab9bf356b7..a027989e39 100644 --- a/src/vs/base/test/node/pfs/pfs.test.ts +++ b/src/vs/base/test/node/pfs/pfs.test.ts @@ -16,7 +16,6 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { isWindows, isLinux } from 'vs/base/common/platform'; import { canNormalize } from 'vs/base/common/normalization'; import { VSBuffer } from 'vs/base/common/buffer'; -import { join } from 'path'; const chunkSize = 64 * 1024; const readError = 'Error while reading'; @@ -387,31 +386,6 @@ suite('PFS', () => { } }); - test('readdirWithFileTypes', async () => { - if (canNormalize && typeof process.versions['electron'] !== 'undefined' /* needs electron */) { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const testDir = join(parentDir, 'pfs', id); - - const newDir = path.join(testDir, 'öäü'); - await pfs.mkdirp(newDir, 493); - - await pfs.writeFile(join(testDir, 'somefile.txt'), 'contents'); - - assert.ok(fs.existsSync(newDir)); - - const children = await pfs.readdirWithFileTypes(testDir); - - assert.equal(children.some(n => n.name === 'öäü'), true); // Mac always converts to NFD, so - assert.equal(children.some(n => n.isDirectory()), true); - - assert.equal(children.some(n => n.name === 'somefile.txt'), true); - assert.equal(children.some(n => n.isFile()), true); - - await pfs.rimraf(parentDir); - } - }); - test('writeFile (string)', async () => { const smallData = 'Hello World'; const bigData = (new Array(100 * 1024)).join('Large String\n'); diff --git a/src/vs/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html index 5b06636edb..9b8e42d3a3 100644 --- a/src/vs/code/browser/workbench/workbench.html +++ b/src/vs/code/browser/workbench/workbench.html @@ -24,9 +24,6 @@ - - - diff --git a/src/vs/code/browser/workbench/workbench.js b/src/vs/code/browser/workbench/workbench.js index 069fb4cff3..9dd81ca5e7 100644 --- a/src/vs/code/browser/workbench/workbench.js +++ b/src/vs/code/browser/workbench/workbench.js @@ -16,6 +16,7 @@ 'xterm-addon-search': `${window.location.origin}/node_modules/xterm-addon-search/lib/xterm-addon-search.js`, 'xterm-addon-web-links': `${window.location.origin}/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`, 'semver-umd': `${window.location.origin}/node_modules/semver-umd/lib/semver-umd.js`, + '@microsoft/applicationinsights-web': `${window.location.origin}/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js`, } }); @@ -24,4 +25,4 @@ api.create(document.body, options); }); -})(); \ No newline at end of file +})(); diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index 7ce1609c4c..44dfc3e8e2 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -64,9 +64,9 @@ export function startup(configuration: IssueReporterConfiguration) { } export class IssueReporter extends Disposable { - private environmentService: IEnvironmentService; - private telemetryService: ITelemetryService; - private logService: ILogService; + private environmentService!: IEnvironmentService; + private telemetryService!: ITelemetryService; + private logService!: ILogService; private readonly issueReporterModel: IssueReporterModel; private numberOfSearchResultsDisplayed = 0; private receivedSystemInfo = false; @@ -74,7 +74,7 @@ export class IssueReporter extends Disposable { private shouldQueueSearch = false; private hasBeenSubmitted = false; - private readonly previewButton: Button; + private readonly previewButton!: Button; constructor(configuration: IssueReporterConfiguration) { super(); diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 0a2b94413f..52f7107704 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -77,7 +77,7 @@ const eventPrefix = 'monacoworkbench'; class MainProcessService implements IMainProcessService { constructor(private server: Server, private mainRouter: StaticRouter) { } - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; getChannel(channelName: string): IChannel { return this.server.getChannel(channelName, this.mainRouter); diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js index 77ce5491cb..790834d2a3 100644 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ b/src/vs/code/electron-browser/workbench/workbench.js @@ -16,9 +16,9 @@ process['lazyEnv'] = getLazyEnv(); // Load workbench main bootstrapWindow.load([ - 'vs/workbench/workbench.main', - 'vs/nls!vs/workbench/workbench.main', - 'vs/css!vs/workbench/workbench.main' + 'vs/workbench/workbench.desktop.main', + 'vs/nls!vs/workbench/workbench.desktop.main', + 'vs/css!vs/workbench/workbench.desktop.main' ], function (workbench, configuration) { perf.mark('didLoadWorkbenchMain'); diff --git a/src/vs/code/electron-main/sharedProcess.ts b/src/vs/code/electron-main/sharedProcess.ts index 6ef4a92a4a..a47fce312d 100644 --- a/src/vs/code/electron-main/sharedProcess.ts +++ b/src/vs/code/electron-main/sharedProcess.ts @@ -18,7 +18,7 @@ export class SharedProcess implements ISharedProcess { private barrier = new Barrier(); - private window: Electron.BrowserWindow | null; + private window: Electron.BrowserWindow | null = null; constructor( private readonly machineId: string, diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index d4b846b4da..61d4020a37 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -360,17 +360,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.pendingLoadConfig = undefined; } - - // To prevent flashing, we set the window visible after the page has finished to load but before Code is loaded - if (this._win && !this._win.isVisible()) { - if (this.windowState.mode === WindowMode.Maximized) { - this._win.maximize(); - } - - if (!this._win.isVisible()) { // maximize also makes visible - this._win.show(); - } - } }); // Window Focus @@ -838,17 +827,16 @@ export class CodeWindow extends Disposable implements ICodeWindow { } private useNativeFullScreen(): boolean { - return true; // TODO@ben enable simple fullscreen again (https://github.com/microsoft/vscode/issues/75054) - // const windowConfig = this.configurationService.getValue('window'); - // if (!windowConfig || typeof windowConfig.nativeFullScreen !== 'boolean') { - // return true; // default - // } + const windowConfig = this.configurationService.getValue('window'); + if (!windowConfig || typeof windowConfig.nativeFullScreen !== 'boolean') { + return true; // default + } - // if (windowConfig.nativeTabs) { - // return true; // https://github.com/electron/electron/issues/16142 - // } + if (windowConfig.nativeTabs) { + return true; // https://github.com/electron/electron/issues/16142 + } - // return windowConfig.nativeFullScreen !== false; + return windowConfig.nativeFullScreen !== false; } isMinimized(): boolean { diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 35f7fdc4ec..d2f0b32de6 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -1149,9 +1149,10 @@ export class WindowsManager extends Disposable implements IWindowsMainService { } } - // Linux/Windows: by default we open files in the new window unless triggered via DIALOG or MENU context + // Linux/Windows: by default we open files in the new window unless triggered via DIALOG / MENU context + // or from the integrated terminal where we assume the user prefers to open in the current window else { - if (openConfig.context !== OpenContext.DIALOG && openConfig.context !== OpenContext.MENU) { + if (openConfig.context !== OpenContext.DIALOG && openConfig.context !== OpenContext.MENU && !(openConfig.userEnv && openConfig.userEnv['TERM_PROGRAM'] === 'vscode')) { openFilesInNewWindow = true; } } @@ -1255,10 +1256,9 @@ export class WindowsManager extends Disposable implements IWindowsMainService { openConfig.cli['file-uri'] = fileUris; // if there are no files or folders cli args left, use the "remote" cli argument - if (!cliArgs.length && !folderUris.length && !fileUris.length) { - if (authority) { - openConfig.cli.remote = authority; - } + const noFilesOrFolders = !cliArgs.length && !folderUris.length && !fileUris.length; + if (noFilesOrFolders && authority) { + openConfig.cli.remote = authority; } // Open it @@ -1266,7 +1266,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { context: openConfig.context, cli: openConfig.cli, forceNewWindow: true, - forceEmpty: !cliArgs.length && !folderUris.length && !fileUris.length, + forceEmpty: noFilesOrFolders, userEnv: openConfig.userEnv, noRecentEntry: true, waitMarkerFileURI: openConfig.waitMarkerFileURI @@ -2116,4 +2116,4 @@ function resourceFromURIToOpen(u: IURIToOpen): URI { } return u.fileUri; -} \ No newline at end of file +} diff --git a/src/vs/code/test/electron-main/nativeHelpers.test.ts b/src/vs/code/test/electron-main/nativeHelpers.test.ts index 8930b344c1..2d7a1412c5 100644 --- a/src/vs/code/test/electron-main/nativeHelpers.test.ts +++ b/src/vs/code/test/electron-main/nativeHelpers.test.ts @@ -7,22 +7,33 @@ import * as assert from 'assert'; import { isWindows } from 'vs/base/common/platform'; suite('Windows Native Helpers', () => { - test('windows-mutex', async () => { - if (!isWindows) { - return; - } + if (!isWindows) { + return; + } + test('windows-mutex', async () => { const mutex = await import('windows-mutex'); - assert.ok(mutex, 'Unable to load windows-mutex dependency.'); + assert.ok(mutex && typeof mutex.isActive === 'function', 'Unable to load windows-mutex dependency.'); assert.ok(typeof mutex.isActive === 'function', 'Unable to load windows-mutex dependency.'); }); test('windows-foreground-love', async () => { - if (!isWindows) { - return; - } - const foregroundLove = await import('windows-foreground-love'); - assert.ok(foregroundLove, 'Unable to load windows-foreground-love dependency.'); + assert.ok(foregroundLove && typeof foregroundLove.allowSetForegroundWindow === 'function', 'Unable to load windows-foreground-love dependency.'); }); -}); \ No newline at end of file + + test('windows-process-tree', async () => { + const processTree = await import('windows-process-tree'); + assert.ok(processTree && typeof processTree.getProcessTree === 'function', 'Unable to load windows-process-tree dependency.'); + }); + + test('vscode-windows-ca-certs', async () => { + const windowsCerts = await import('vscode-windows-ca-certs'); + assert.ok(windowsCerts, 'Unable to load vscode-windows-ca-certs dependency.'); + }); + + test('vscode-windows-registry', async () => { + const windowsRegistry = await import('vscode-windows-registry'); + assert.ok(windowsRegistry && typeof windowsRegistry.GetStringRegKey === 'function', 'Unable to load vscode-windows-registry dependency.'); + }); +}); diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index 0baf78d1ad..06776f19f0 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -16,6 +16,7 @@ import { Range as EditorRange } from 'vs/editor/common/core/range'; import { HorizontalRange } from 'vs/editor/common/view/renderingContext'; import { ViewContext } from 'vs/editor/common/view/viewContext'; import { IViewModel } from 'vs/editor/common/viewModel/viewModel'; +import { CursorColumns } from 'vs/editor/common/controller/cursorCommon'; export interface IViewZoneData { viewZoneId: number; @@ -410,7 +411,7 @@ class HitTestRequest extends BareHitTestRequest { let mouseColumn = this.mouseColumn; if (position && position.column < this._ctx.model.getLineMaxColumn(position.lineNumber)) { // Most likely, the line contains foreign decorations... - mouseColumn = position.column; + mouseColumn = CursorColumns.visibleColumnFromColumn(this._ctx.model.getLineContent(position.lineNumber), position.column, this._ctx.model.getOptions().tabSize) + 1; } return new MouseTarget(this.target, type, mouseColumn, position, range, detail); } diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 9573f53e84..80aab9c08f 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -446,14 +446,8 @@ export class TextAreaHandler extends ViewPart { private _primaryCursorVisibleRange: HorizontalRange | null = null; public prepareRender(ctx: RenderingContext): void { - if (this._accessibilitySupport === AccessibilitySupport.Enabled) { - // Do not move the textarea with the cursor, as this generates accessibility events that might confuse screen readers - // See https://github.com/Microsoft/vscode/issues/26730 - this._primaryCursorVisibleRange = null; - } else { - const primaryCursorPosition = new Position(this._selections[0].positionLineNumber, this._selections[0].positionColumn); - this._primaryCursorVisibleRange = ctx.visibleRangeForPosition(primaryCursorPosition); - } + const primaryCursorPosition = new Position(this._selections[0].positionLineNumber, this._selections[0].positionColumn); + this._primaryCursorVisibleRange = ctx.visibleRangeForPosition(primaryCursorPosition); } public render(ctx: RestrictedRenderingContext): void { diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index 9c3a61162a..56b631a78c 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -119,6 +119,7 @@ export class TextAreaInput extends Disposable { this._asyncTriggerCut = this._register(new RunOnceScheduler(() => this._onCut.fire(), 0)); this._textAreaState = TextAreaState.EMPTY; + this._selectionChangeListener = null; this.writeScreenReaderContent('ctor'); this._hasFocus = false; diff --git a/src/vs/editor/browser/core/editorState.ts b/src/vs/editor/browser/core/editorState.ts index 8eb4a24723..ae39a4c666 100644 --- a/src/vs/editor/browser/core/editorState.ts +++ b/src/vs/editor/browser/core/editorState.ts @@ -35,16 +35,25 @@ export class EditorState { if ((this.flags & CodeEditorStateFlag.Value) !== 0) { const model = editor.getModel(); this.modelVersionId = model ? strings.format('{0}#{1}', model.uri.toString(), model.getVersionId()) : null; + } else { + this.modelVersionId = null; } if ((this.flags & CodeEditorStateFlag.Position) !== 0) { this.position = editor.getPosition(); + } else { + this.position = null; } if ((this.flags & CodeEditorStateFlag.Selection) !== 0) { this.selection = editor.getSelection(); + } else { + this.selection = null; } if ((this.flags & CodeEditorStateFlag.Scroll) !== 0) { this.scrollLeft = editor.getScrollLeft(); this.scrollTop = editor.getScrollTop(); + } else { + this.scrollLeft = -1; + this.scrollTop = -1; } } diff --git a/src/vs/editor/browser/services/codeEditorServiceImpl.ts b/src/vs/editor/browser/services/codeEditorServiceImpl.ts index 147e7bb855..8f11c89704 100644 --- a/src/vs/editor/browser/services/codeEditorServiceImpl.ts +++ b/src/vs/editor/browser/services/codeEditorServiceImpl.ts @@ -127,13 +127,13 @@ class DecorationTypeOptionsProvider implements IModelDecorationOptionsProvider { public refCount: number; public className: string | undefined; - public inlineClassName: string; - public inlineClassNameAffectsLetterSpacing: boolean; + public inlineClassName: string | undefined; + public inlineClassNameAffectsLetterSpacing: boolean | undefined; public beforeContentClassName: string | undefined; public afterContentClassName: string | undefined; public glyphMarginClassName: string | undefined; public isWholeLine: boolean; - public overviewRuler: IModelDecorationOverviewRulerOptions; + public overviewRuler: IModelDecorationOverviewRulerOptions | undefined; public stickiness: TrackedRangeStickiness | undefined; constructor(themeService: IThemeService, providerArgs: ProviderArguments) { diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index f223361745..1f5f2286b9 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -128,22 +128,6 @@ export class View extends ViewEventHandler { this._textAreaHandler = new TextAreaHandler(this._context, viewController, this.createTextAreaHandlerHelper()); this.viewParts.push(this._textAreaHandler); - this.createViewParts(); - this._setLayout(); - - // Pointer handler - this.pointerHandler = this._register(new PointerHandler(this._context, viewController, this.createPointerHandlerHelper())); - - this._register(model.addEventListener((events: viewEvents.ViewEvent[]) => { - this.eventDispatcher.emitMany(events); - })); - - this._register(this._cursor.addEventListener((events: viewEvents.ViewEvent[]) => { - this.eventDispatcher.emitMany(events); - })); - } - - private createViewParts(): void { // These two dom nodes must be constructed up front, since references are needed in the layout provider (scrolling & co.) this.linesContent = createFastDomNode(document.createElement('div')); this.linesContent.setClassName('lines-content' + ' monaco-editor-background'); @@ -233,6 +217,19 @@ export class View extends ViewEventHandler { this.overflowGuardContainer.appendChild(minimap.getDomNode()); this.domNode.appendChild(this.overflowGuardContainer); this.domNode.appendChild(this.contentWidgets.overflowingContentWidgetsDomNode); + + this._setLayout(); + + // Pointer handler + this.pointerHandler = this._register(new PointerHandler(this._context, viewController, this.createPointerHandlerHelper())); + + this._register(model.addEventListener((events: viewEvents.ViewEvent[]) => { + this.eventDispatcher.emitMany(events); + })); + + this._register(this._cursor.addEventListener((events: viewEvents.ViewEvent[]) => { + this.eventDispatcher.emitMany(events); + })); } private _flushAccumulatedAndRenderNow(): void { @@ -541,7 +538,7 @@ export class View extends ViewEventHandler { public layoutContentWidget(widgetData: IContentWidgetData): void { const newPosition = widgetData.position ? widgetData.position.position : null; - const newRange = widgetData.position ? widgetData.position.range : null; + const newRange = widgetData.position ? widgetData.position.range || null : null; const newPreference = widgetData.position ? widgetData.position.preference : null; this.contentWidgets.setWidgetPosition(widgetData.widget, newPosition, newRange, newPreference); this._scheduleRender(); diff --git a/src/vs/editor/browser/view/viewLayer.ts b/src/vs/editor/browser/view/viewLayer.ts index aee0bcb49f..9eaf36289d 100644 --- a/src/vs/editor/browser/view/viewLayer.ts +++ b/src/vs/editor/browser/view/viewLayer.ts @@ -34,8 +34,8 @@ export interface ILine { export class RenderedLinesCollection { private readonly _createLine: () => T; - private _lines: T[]; - private _rendLineNumberStart: number; + private _lines!: T[]; + private _rendLineNumberStart!: number; constructor(createLine: () => T) { this._createLine = createLine; diff --git a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts index cdcae0da5f..ca4c277539 100644 --- a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts +++ b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts @@ -14,7 +14,6 @@ import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/v import { ViewContext } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; -import { withUndefinedAsNull } from 'vs/base/common/types'; class Coordinate { _coordinateBrand: void; @@ -111,7 +110,7 @@ export class ViewContentWidgets extends ViewPart { this.setShouldRender(); } - public setWidgetPosition(widget: IContentWidget, position: IPosition | null | undefined, range: IRange | null | undefined, preference: ContentWidgetPositionPreference[] | null | undefined): void { + public setWidgetPosition(widget: IContentWidget, position: IPosition | null, range: IRange | null, preference: ContentWidgetPositionPreference[] | null): void { const myWidget = this._widgets[widget.getId()]; myWidget.setPosition(position, range, preference); @@ -202,8 +201,8 @@ class Widget { this._context = context; this._viewDomNode = viewDomNode; this._actual = actual; - this.domNode = createFastDomNode(this._actual.getDomNode()); + this.domNode = createFastDomNode(this._actual.getDomNode()); this.id = this._actual.getId(); this.allowEditorOverflow = this._actual.allowEditorOverflow || false; this.suppressMouseDown = this._actual.suppressMouseDown || false; @@ -213,7 +212,10 @@ class Widget { this._contentLeft = this._context.configuration.editor.layoutInfo.contentLeft; this._lineHeight = this._context.configuration.editor.lineHeight; - this._setPosition(null, null); + this._position = null; + this._range = null; + this._viewPosition = null; + this._viewRange = null; this._preference = []; this._cachedDomNodeClientWidth = -1; this._cachedDomNodeClientHeight = -1; @@ -242,9 +244,9 @@ class Widget { this._setPosition(this._position, this._range); } - private _setPosition(position: IPosition | null | undefined, range: IRange | null | undefined): void { - this._position = withUndefinedAsNull(position); - this._range = withUndefinedAsNull(range); + private _setPosition(position: IPosition | null, range: IRange | null): void { + this._position = position; + this._range = range; this._viewPosition = null; this._viewRange = null; @@ -270,9 +272,9 @@ class Widget { ); } - public setPosition(position: IPosition | null | undefined, range: IRange | null | undefined, preference: ContentWidgetPositionPreference[] | null | undefined): void { + public setPosition(position: IPosition | null, range: IRange | null, preference: ContentWidgetPositionPreference[] | null): void { this._setPosition(position, range); - this._preference = withUndefinedAsNull(preference); + this._preference = preference; this._cachedDomNodeClientWidth = -1; this._cachedDomNodeClientHeight = -1; } diff --git a/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts b/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts index 6fcf3a3084..965c859a69 100644 --- a/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts +++ b/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts @@ -21,6 +21,7 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { private _renderResult: string[] | null; private _enabled: boolean; private _activeIndentEnabled: boolean; + private _maxIndentLeft: number; constructor(context: ViewContext) { super(); @@ -30,6 +31,9 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { this._spaceWidth = this._context.configuration.editor.fontInfo.spaceWidth; this._enabled = this._context.configuration.editor.viewInfo.renderIndentGuides; this._activeIndentEnabled = this._context.configuration.editor.viewInfo.highlightActiveIndentGuide; + const wrappingColumn = this._context.configuration.editor.wrappingInfo.wrappingColumn; + this._maxIndentLeft = wrappingColumn === -1 ? -1 : (wrappingColumn * this._context.configuration.editor.fontInfo.typicalHalfwidthCharacterWidth); + this._renderResult = null; this._context.addEventHandler(this); @@ -54,6 +58,10 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { this._enabled = this._context.configuration.editor.viewInfo.renderIndentGuides; this._activeIndentEnabled = this._context.configuration.editor.viewInfo.highlightActiveIndentGuide; } + if (e.wrappingInfo || e.fontInfo) { + const wrappingColumn = this._context.configuration.editor.wrappingInfo.wrappingColumn; + this._maxIndentLeft = wrappingColumn === -1 ? -1 : (wrappingColumn * this._context.configuration.editor.fontInfo.typicalHalfwidthCharacterWidth); + } return true; } public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { @@ -133,7 +141,7 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { const className = (containsActiveIndentGuide && i === activeIndentLevel ? 'cigra' : 'cigr'); result += `
`; left += indentWidth; - if (left > scrollWidth) { + if (left > scrollWidth || (this._maxIndentLeft > 0 && left > this._maxIndentLeft)) { break; } } diff --git a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts index d8db7d48f0..6797ddf55e 100644 --- a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts +++ b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts @@ -20,12 +20,12 @@ export class LineNumbersOverlay extends DynamicViewOverlay { private readonly _context: ViewContext; - private _lineHeight: number; - private _renderLineNumbers: RenderLineNumbersType; - private _renderCustomLineNumbers: ((lineNumber: number) => string) | null; - private _renderFinalNewline: boolean; - private _lineNumbersLeft: number; - private _lineNumbersWidth: number; + private _lineHeight!: number; + private _renderLineNumbers!: RenderLineNumbersType; + private _renderCustomLineNumbers!: ((lineNumber: number) => string) | null; + private _renderFinalNewline!: boolean; + private _lineNumbersLeft!: number; + private _lineNumbersWidth!: number; private _lastCursorModelPosition: Position; private _renderResult: string[] | null; diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.ts b/src/vs/editor/browser/viewParts/lines/viewLines.ts index 6f37e66207..72928a9fda 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLines.ts @@ -71,6 +71,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, private _typicalHalfwidthCharacterWidth: number; private _isViewportWrapping: boolean; private _revealHorizontalRightPadding: number; + private _scrollOff: number; private _canUseLayerHinting: boolean; private _viewLineOptions: ViewLineOptions; @@ -94,6 +95,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, this._typicalHalfwidthCharacterWidth = conf.editor.fontInfo.typicalHalfwidthCharacterWidth; this._isViewportWrapping = conf.editor.wrappingInfo.isViewportWrapping; this._revealHorizontalRightPadding = conf.editor.viewInfo.revealHorizontalRightPadding; + this._scrollOff = conf.editor.viewInfo.cursorSurroundingLines; this._canUseLayerHinting = conf.editor.canUseLayerHinting; this._viewLineOptions = new ViewLineOptions(conf, this._context.theme.type); @@ -150,6 +152,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, } if (e.viewInfo) { this._revealHorizontalRightPadding = conf.editor.viewInfo.revealHorizontalRightPadding; + this._scrollOff = conf.editor.viewInfo.cursorSurroundingLines; } if (e.canUseLayerHinting) { this._canUseLayerHinting = conf.editor.canUseLayerHinting; @@ -596,6 +599,11 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, // Have a box that includes one extra line height (for the horizontal scrollbar) boxStartY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.startLineNumber); boxEndY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.endLineNumber) + this._lineHeight; + + const context = Math.min((viewportHeight / this._lineHeight) / 2, this._scrollOff); + boxStartY -= context * this._lineHeight; + boxEndY += Math.max(0, (context - 1)) * this._lineHeight; + if (verticalType === viewEvents.VerticalRevealType.Simple || verticalType === viewEvents.VerticalRevealType.Bottom) { // Reveal one line more when the last line would be covered by the scrollbar - arrow down case or revealing a line explicitly at bottom boxEndY += this._lineHeight; diff --git a/src/vs/editor/browser/viewParts/margin/margin.ts b/src/vs/editor/browser/viewParts/margin/margin.ts index bf662035bc..6abfb30584 100644 --- a/src/vs/editor/browser/viewParts/margin/margin.ts +++ b/src/vs/editor/browser/viewParts/margin/margin.ts @@ -28,7 +28,16 @@ export class Margin extends ViewPart { this._glyphMarginLeft = this._context.configuration.editor.layoutInfo.glyphMarginLeft; this._glyphMarginWidth = this._context.configuration.editor.layoutInfo.glyphMarginWidth; - this._domNode = this._createDomNode(); + this._domNode = createFastDomNode(document.createElement('div')); + this._domNode.setClassName(Margin.OUTER_CLASS_NAME); + this._domNode.setPosition('absolute'); + this._domNode.setAttribute('role', 'presentation'); + this._domNode.setAttribute('aria-hidden', 'true'); + + this._glyphMarginBackgroundDomNode = createFastDomNode(document.createElement('div')); + this._glyphMarginBackgroundDomNode.setClassName(Margin.CLASS_NAME); + + this._domNode.appendChild(this._glyphMarginBackgroundDomNode); } public dispose(): void { @@ -39,20 +48,6 @@ export class Margin extends ViewPart { return this._domNode; } - private _createDomNode(): FastDomNode { - const domNode = createFastDomNode(document.createElement('div')); - domNode.setClassName(Margin.OUTER_CLASS_NAME); - domNode.setPosition('absolute'); - domNode.setAttribute('role', 'presentation'); - domNode.setAttribute('aria-hidden', 'true'); - - this._glyphMarginBackgroundDomNode = createFastDomNode(document.createElement('div')); - this._glyphMarginBackgroundDomNode.setClassName(Margin.CLASS_NAME); - - domNode.appendChild(this._glyphMarginBackgroundDomNode); - return domNode; - } - // --- begin event handlers public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 32766b14c2..71e926f5ff 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -742,10 +742,6 @@ export class Minimap extends ViewPart { canvasContext.clearRect(0, 0, canvasInnerWidth, canvasInnerHeight); - // If the minimap is rendered using blocks, text takes up half the line height - const lineHeightRatio = renderMinimap === RenderMinimap.LargeBlocks || renderMinimap === RenderMinimap.SmallBlocks ? 0.5 : 1; - const height = lineHeight * lineHeightRatio; - // Loop over decorations, ignoring those that don't have the minimap property set and rendering rectangles for each line the decoration spans const lineOffsetMap = new Map(); for (let i = 0; i < decorations.length; i++) { @@ -756,7 +752,7 @@ export class Minimap extends ViewPart { } for (let line = decoration.range.startLineNumber; line <= decoration.range.endLineNumber; line++) { - this.renderDecorationOnLine(canvasContext, lineOffsetMap, decoration, layout, line, height, lineHeight, tabSize, characterWidth); + this.renderDecorationOnLine(canvasContext, lineOffsetMap, decoration, layout, line, lineHeight, lineHeight, tabSize, characterWidth); } } diff --git a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts index 782ae11ebf..024cec2cfc 100644 --- a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts +++ b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts @@ -201,7 +201,7 @@ export class DecorationsOverviewRuler extends ViewPart { private readonly _tokensColorTrackerListener: IDisposable; private readonly _domNode: FastDomNode; - private _settings: Settings; + private _settings!: Settings; private _cursorPositions: Position[]; constructor(context: ViewContext) { diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts index 547d46d9e1..a16a6ab6fb 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts @@ -68,7 +68,7 @@ export class ViewCursor { Configuration.applyFontInfo(this._domNode, this._context.configuration.editor.fontInfo); this._domNode.setDisplay('none'); - this.updatePosition(new Position(1, 1)); + this._position = new Position(1, 1); this._lastRenderedContent = ''; this._renderData = null; @@ -113,7 +113,7 @@ export class ViewCursor { } public onCursorPositionChanged(position: Position): boolean { - this.updatePosition(position); + this._position = position; return true; } @@ -210,8 +210,4 @@ export class ViewCursor { width: 2 }; } - - private updatePosition(newPosition: Position): void { - this._position = newPosition; - } } diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts index 390f00c6b1..529d84eba4 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts @@ -49,6 +49,8 @@ export class ViewCursors extends ViewPart { this._cursorSmoothCaretAnimation = this._context.configuration.editor.viewInfo.cursorSmoothCaretAnimation; this._selectionIsEmpty = true; + this._isVisible = false; + this._primaryCursor = new ViewCursor(this._context); this._secondaryCursors = []; this._renderData = []; diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 6f083a7672..4cc1e4f055 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -279,7 +279,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._instantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this._contextKeyService])); - this._attachModel(null); + this._modelData = null; this._contributions = {}; this._actions = {}; diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 6b69e41884..2255370655 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -8,7 +8,7 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; import { ISashEvent, IVerticalSashLayoutProvider, Sash, SashState } from 'vs/base/browser/ui/sash/sash'; -import { RunOnceScheduler } from 'vs/base/common/async'; +import { RunOnceScheduler, IntervalTimer } from 'vs/base/common/async'; import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -159,17 +159,17 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE private _width: number; private _height: number; private _reviewHeight: number; - private readonly _measureDomElementToken: number; + private readonly _measureDomElementToken: IntervalTimer | null; - private originalEditor: CodeEditorWidget; - private _originalDomNode: HTMLElement; + private readonly originalEditor: CodeEditorWidget; + private readonly _originalDomNode: HTMLElement; private readonly _originalEditorState: VisualEditorState; - private _originalOverviewRuler: editorBrowser.IOverviewRuler; + private _originalOverviewRuler: editorBrowser.IOverviewRuler | null; - private modifiedEditor: CodeEditorWidget; - private _modifiedDomNode: HTMLElement; + private readonly modifiedEditor: CodeEditorWidget; + private readonly _modifiedDomNode: HTMLElement; private readonly _modifiedEditorState: VisualEditorState; - private _modifiedOverviewRuler: editorBrowser.IOverviewRuler; + private _modifiedOverviewRuler: editorBrowser.IOverviewRuler | null; private _currentlyChangingViewZones: boolean; private _beginUpdateDecorationsTimeout: number; @@ -185,7 +185,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE private _renderSideBySide: boolean; private _renderIndicators: boolean; private _enableSplitViewResizing: boolean; - private _strategy: IDiffEditorWidgetStyle; + private _strategy!: IDiffEditorWidgetStyle; private readonly _updateDecorationsRunner: RunOnceScheduler; @@ -271,8 +271,19 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE })); this._containerDomElement.appendChild(this._overviewDomElement); - this._createLeftHandSide(); - this._createRightHandSide(); + // Create left side + this._originalDomNode = document.createElement('div'); + this._originalDomNode.className = 'editor original'; + this._originalDomNode.style.position = 'absolute'; + this._originalDomNode.style.height = '100%'; + this._containerDomElement.appendChild(this._originalDomNode); + + // Create right side + this._modifiedDomNode = document.createElement('div'); + this._modifiedDomNode.className = 'editor modified'; + this._modifiedDomNode.style.position = 'absolute'; + this._modifiedDomNode.style.height = '100%'; + this._containerDomElement.appendChild(this._modifiedDomNode); this._beginUpdateDecorationsTimeout = -1; this._currentlyChangingViewZones = false; @@ -304,8 +315,11 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE rightServices.set(IContextKeyService, rightContextKeyService); const rightScopedInstantiationService = instantiationService.createChild(rightServices); - this._createLeftHandSideEditor(options, leftScopedInstantiationService); - this._createRightHandSideEditor(options, rightScopedInstantiationService); + this.originalEditor = this._createLeftHandSideEditor(options, leftScopedInstantiationService); + this.modifiedEditor = this._createRightHandSideEditor(options, rightScopedInstantiationService); + + this._originalOverviewRuler = null; + this._modifiedOverviewRuler = null; this._reviewPane = new DiffReview(this); this._containerDomElement.appendChild(this._reviewPane.domNode.domNode); @@ -313,7 +327,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._containerDomElement.appendChild(this._reviewPane.actionBarContainer.domNode); if (options.automaticLayout) { - this._measureDomElementToken = window.setInterval(() => this._measureDomElement(false), 100); + this._measureDomElementToken = new IntervalTimer(); + this._measureDomElementToken.cancelAndSet(() => this._measureDomElement(false), 100); + } else { + this._measureDomElementToken = null; } // enableSplitViewResizing @@ -393,26 +410,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._layoutOverviewRulers(); } - private _createLeftHandSide(): void { - this._originalDomNode = document.createElement('div'); - this._originalDomNode.className = 'editor original'; - this._originalDomNode.style.position = 'absolute'; - this._originalDomNode.style.height = '100%'; - this._containerDomElement.appendChild(this._originalDomNode); - } + private _createLeftHandSideEditor(options: editorOptions.IDiffEditorOptions, instantiationService: IInstantiationService): CodeEditorWidget { + const editor = this._createInnerEditor(instantiationService, this._originalDomNode, this._adjustOptionsForLeftHandSide(options, this._originalIsEditable)); - private _createRightHandSide(): void { - this._modifiedDomNode = document.createElement('div'); - this._modifiedDomNode.className = 'editor modified'; - this._modifiedDomNode.style.position = 'absolute'; - this._modifiedDomNode.style.height = '100%'; - this._containerDomElement.appendChild(this._modifiedDomNode); - } - - private _createLeftHandSideEditor(options: editorOptions.IDiffEditorOptions, instantiationService: IInstantiationService): void { - this.originalEditor = this._createInnerEditor(instantiationService, this._originalDomNode, this._adjustOptionsForLeftHandSide(options, this._originalIsEditable)); - - this._register(this.originalEditor.onDidScrollChange((e) => { + this._register(editor.onDidScrollChange((e) => { if (this._isHandlingScrollEvent) { return; } @@ -429,21 +430,23 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._layoutOverviewViewport(); })); - this._register(this.originalEditor.onDidChangeViewZones(() => { + this._register(editor.onDidChangeViewZones(() => { this._onViewZonesChanged(); })); - this._register(this.originalEditor.onDidChangeModelContent(() => { + this._register(editor.onDidChangeModelContent(() => { if (this._isVisible) { this._beginUpdateDecorationsSoon(); } })); + + return editor; } - private _createRightHandSideEditor(options: editorOptions.IDiffEditorOptions, instantiationService: IInstantiationService): void { - this.modifiedEditor = this._createInnerEditor(instantiationService, this._modifiedDomNode, this._adjustOptionsForRightHandSide(options)); + private _createRightHandSideEditor(options: editorOptions.IDiffEditorOptions, instantiationService: IInstantiationService): CodeEditorWidget { + const editor = this._createInnerEditor(instantiationService, this._modifiedDomNode, this._adjustOptionsForRightHandSide(options)); - this._register(this.modifiedEditor.onDidScrollChange((e) => { + this._register(editor.onDidScrollChange((e) => { if (this._isHandlingScrollEvent) { return; } @@ -460,21 +463,23 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._layoutOverviewViewport(); })); - this._register(this.modifiedEditor.onDidChangeViewZones(() => { + this._register(editor.onDidChangeViewZones(() => { this._onViewZonesChanged(); })); - this._register(this.modifiedEditor.onDidChangeConfiguration((e) => { - if (e.fontInfo && this.modifiedEditor.getModel()) { + this._register(editor.onDidChangeConfiguration((e) => { + if (e.fontInfo && editor.getModel()) { this._onViewZonesChanged(); } })); - this._register(this.modifiedEditor.onDidChangeModelContent(() => { + this._register(editor.onDidChangeModelContent(() => { if (this._isVisible) { this._beginUpdateDecorationsSoon(); } })); + + return editor; } protected _createInnerEditor(instantiationService: IInstantiationService, container: HTMLElement, options: editorOptions.IEditorOptions): CodeEditorWidget { @@ -489,7 +494,9 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._beginUpdateDecorationsTimeout = -1; } - window.clearInterval(this._measureDomElementToken); + if (this._measureDomElementToken) { + this._measureDomElementToken.dispose(); + } this._cleanViewZonesAndDecorations(); @@ -825,6 +832,9 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } private _layoutOverviewRulers(): void { + if (!this._originalOverviewRuler || !this._modifiedOverviewRuler) { + return; + } let freeSpace = DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH - 2 * DiffEditorWidget.ONE_OVERVIEW_WIDTH; let layoutInfo = this.modifiedEditor.getLayoutInfo(); if (layoutInfo) { @@ -926,7 +936,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } private _updateDecorations(): void { - if (!this.originalEditor.getModel() || !this.modifiedEditor.getModel()) { + if (!this.originalEditor.getModel() || !this.modifiedEditor.getModel() || !this._originalOverviewRuler || !this._modifiedOverviewRuler) { return; } const lineChanges = (this._diffComputationResult ? this._diffComputationResult.changes : []); @@ -1194,12 +1204,14 @@ interface IDataSource { abstract class DiffEditorWidgetStyle extends Disposable implements IDiffEditorWidgetStyle { _dataSource: IDataSource; - _insertColor: Color; - _removeColor: Color; + _insertColor: Color | null; + _removeColor: Color | null; constructor(dataSource: IDataSource) { super(); this._dataSource = dataSource; + this._insertColor = null; + this._removeColor = null; } public applyColors(theme: ITheme): boolean { @@ -1277,6 +1289,7 @@ class ForeignViewZonesIterator { constructor(source: IEditorWhitespace[]) { this._source = source; this._index = -1; + this.current = null; this.advance(); } @@ -1555,7 +1568,7 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffE private readonly _sash: Sash; private _sashRatio: number | null; private _sashPosition: number | null; - private _startSashPosition: number; + private _startSashPosition: number | null; constructor(dataSource: IDataSource, enableSplitViewResizing: boolean) { super(dataSource); @@ -1563,6 +1576,7 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffE this._disableSash = (enableSplitViewResizing === false); this._sashRatio = null; this._sashPosition = null; + this._startSashPosition = null; this._sash = this._register(new Sash(this._dataSource.getContainerDomNode(), this)); if (this._disableSash) { @@ -1619,7 +1633,7 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffE private onSashDrag(e: ISashEvent): void { let w = this._dataSource.getWidth(); let contentWidth = w - DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH; - let sashPosition = this.layout((this._startSashPosition + (e.currentX - e.startX)) / contentWidth); + let sashPosition = this.layout((this._startSashPosition! + (e.currentX - e.startX)) / contentWidth); this._sashRatio = sashPosition / contentWidth; @@ -1654,7 +1668,7 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffE } protected _getOriginalEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorDiffDecorations { - const overviewZoneColor = this._removeColor.toString(); + const overviewZoneColor = String(this._removeColor); let result: IEditorDiffDecorations = { decorations: [], @@ -1714,7 +1728,7 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffE } protected _getModifiedEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorDiffDecorations { - const overviewZoneColor = this._insertColor.toString(); + const overviewZoneColor = String(this._insertColor); let result: IEditorDiffDecorations = { decorations: [], @@ -1834,7 +1848,7 @@ class DiffEditorWidgetInline extends DiffEditorWidgetStyle implements IDiffEdito } protected _getOriginalEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorDiffDecorations { - const overviewZoneColor = this._removeColor.toString(); + const overviewZoneColor = String(this._removeColor); let result: IEditorDiffDecorations = { decorations: [], @@ -1863,7 +1877,7 @@ class DiffEditorWidgetInline extends DiffEditorWidgetStyle implements IDiffEdito } protected _getModifiedEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorDiffDecorations { - const overviewZoneColor = this._insertColor.toString(); + const overviewZoneColor = String(this._insertColor); let result: IEditorDiffDecorations = { decorations: [], diff --git a/src/vs/editor/common/commands/replaceCommand.ts b/src/vs/editor/common/commands/replaceCommand.ts index 08cbf3958d..c34bb857b2 100644 --- a/src/vs/editor/common/commands/replaceCommand.ts +++ b/src/vs/editor/common/commands/replaceCommand.ts @@ -101,12 +101,13 @@ export class ReplaceCommandThatPreservesSelection implements ICommand { private readonly _range: Range; private readonly _text: string; private readonly _initialSelection: Selection; - private _selectionId: string; + private _selectionId: string | null; constructor(editRange: Range, text: string, initialSelection: Selection) { this._range = editRange; this._text = text; this._initialSelection = initialSelection; + this._selectionId = null; } public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void { @@ -115,6 +116,6 @@ export class ReplaceCommandThatPreservesSelection implements ICommand { } public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { - return helper.getTrackedSelection(this._selectionId); + return helper.getTrackedSelection(this._selectionId!); } } diff --git a/src/vs/editor/common/commands/shiftCommand.ts b/src/vs/editor/common/commands/shiftCommand.ts index b9e33100e1..6589c265a3 100644 --- a/src/vs/editor/common/commands/shiftCommand.ts +++ b/src/vs/editor/common/commands/shiftCommand.ts @@ -70,13 +70,14 @@ export class ShiftCommand implements ICommand { private readonly _opts: IShiftCommandOpts; private readonly _selection: Selection; - private _selectionId: string; + private _selectionId: string | null; private _useLastEditRangeForCursorEndPosition: boolean; private _selectionStartColumnStaysPut: boolean; constructor(range: Selection, opts: IShiftCommandOpts) { this._opts = opts; this._selection = range; + this._selectionId = null; this._useLastEditRangeForCursorEndPosition = false; this._selectionStartColumnStaysPut = false; } @@ -241,7 +242,7 @@ export class ShiftCommand implements ICommand { let lastOp = helper.getInverseEditOperations()[0]; return new Selection(lastOp.range.endLineNumber, lastOp.range.endColumn, lastOp.range.endLineNumber, lastOp.range.endColumn); } - const result = helper.getTrackedSelection(this._selectionId); + const result = helper.getTrackedSelection(this._selectionId!); if (this._selectionStartColumnStaysPut) { // The selection start should not move diff --git a/src/vs/editor/common/commands/trimTrailingWhitespaceCommand.ts b/src/vs/editor/common/commands/trimTrailingWhitespaceCommand.ts index f3f72949a3..d34cdc1434 100644 --- a/src/vs/editor/common/commands/trimTrailingWhitespaceCommand.ts +++ b/src/vs/editor/common/commands/trimTrailingWhitespaceCommand.ts @@ -13,28 +13,29 @@ import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/mod export class TrimTrailingWhitespaceCommand implements ICommand { - private readonly selection: Selection; - private selectionId: string; - private readonly cursors: Position[]; + private readonly _selection: Selection; + private _selectionId: string | null; + private readonly _cursors: Position[]; constructor(selection: Selection, cursors: Position[]) { - this.selection = selection; - this.cursors = cursors; + this._selection = selection; + this._cursors = cursors; + this._selectionId = null; } public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void { - let ops = trimTrailingWhitespace(model, this.cursors); + let ops = trimTrailingWhitespace(model, this._cursors); for (let i = 0, len = ops.length; i < len; i++) { let op = ops[i]; builder.addEditOperation(op.range, op.text); } - this.selectionId = builder.trackSelection(this.selection); + this._selectionId = builder.trackSelection(this._selection); } public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { - return helper.getTrackedSelection(this.selectionId); + return helper.getTrackedSelection(this._selectionId!); } } diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index a8daa9c55c..c518523da5 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -68,7 +68,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed public readonly isSimpleWidget: boolean; protected _rawOptions: editorOptions.IEditorOptions; protected _validatedOptions: editorOptions.IValidatedEditorOptions; - public editor: editorOptions.InternalEditorOptions; + public editor!: editorOptions.InternalEditorOptions; private _isDominatedByLongLines: boolean; private _lineNumbersDigitCount: number; @@ -268,6 +268,11 @@ const editorConfiguration: IConfigurationNode = { 'default': 'on', 'description': nls.localize('lineNumbers', "Controls the display of line numbers.") }, + 'editor.cursorSurroundingLines': { + 'type': 'number', + 'default': EDITOR_DEFAULTS.viewInfo.cursorSurroundingLines, + 'description': nls.localize('cursorSurroundingLines', "Controls the minimal number of visible leading and trailing lines surrounding the cursor. Known as 'scrollOff' or `scrollOffset` in some other editors.") + }, 'editor.renderFinalNewline': { 'type': 'boolean', 'default': EDITOR_DEFAULTS.viewInfo.renderFinalNewline, diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 61d55a5bc3..bb240ebf7c 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -268,6 +268,11 @@ export interface IEditorOptions { * Defaults to true. */ lineNumbers?: 'on' | 'off' | 'relative' | 'interval' | ((lineNumber: number) => string); + /** + * Controls the minimal number of visible leading and trailing lines surrounding the cursor. + * Defaults to 0. + */ + cursorSurroundingLines?: number; /** * Render last line number when the file ends with a newline. * Defaults to true. @@ -988,6 +993,7 @@ export interface InternalEditorViewOptions { readonly ariaLabel: string; readonly renderLineNumbers: RenderLineNumbersType; readonly renderCustomLineNumbers: ((lineNumber: number) => string) | null; + readonly cursorSurroundingLines: number; readonly renderFinalNewline: boolean; readonly selectOnLineNumbers: boolean; readonly glyphMargin: boolean; @@ -1296,6 +1302,7 @@ export class InternalEditorOptions { && a.ariaLabel === b.ariaLabel && a.renderLineNumbers === b.renderLineNumbers && a.renderCustomLineNumbers === b.renderCustomLineNumbers + && a.cursorSurroundingLines === b.cursorSurroundingLines && a.renderFinalNewline === b.renderFinalNewline && a.selectOnLineNumbers === b.selectOnLineNumbers && a.glyphMargin === b.glyphMargin @@ -2055,6 +2062,7 @@ export class EditorOptionsValidator { disableMonospaceOptimizations: disableMonospaceOptimizations, rulers: rulers, ariaLabel: _string(opts.ariaLabel, defaults.ariaLabel), + cursorSurroundingLines: _clampedInt(opts.cursorSurroundingLines, defaults.cursorWidth, 0, Number.MAX_VALUE), renderLineNumbers: renderLineNumbers, renderCustomLineNumbers: renderCustomLineNumbers, renderFinalNewline: _boolean(opts.renderFinalNewline, defaults.renderFinalNewline), @@ -2178,6 +2186,7 @@ export class InternalEditorOptionsFactory { ariaLabel: (accessibilityIsOff ? nls.localize('accessibilityOffAriaLabel', "The editor is not accessible at this time. Press Alt+F1 for options.") : opts.viewInfo.ariaLabel), renderLineNumbers: opts.viewInfo.renderLineNumbers, renderCustomLineNumbers: opts.viewInfo.renderCustomLineNumbers, + cursorSurroundingLines: opts.viewInfo.cursorSurroundingLines, renderFinalNewline: opts.viewInfo.renderFinalNewline, selectOnLineNumbers: opts.viewInfo.selectOnLineNumbers, glyphMargin: opts.viewInfo.glyphMargin, @@ -2197,7 +2206,7 @@ export class InternalEditorOptionsFactory { stopRenderingLineAfter: opts.viewInfo.stopRenderingLineAfter, renderWhitespace: (accessibilityIsOn ? 'none' : opts.viewInfo.renderWhitespace), // DISABLED WHEN SCREEN READER IS ATTACHED renderControlCharacters: (accessibilityIsOn ? false : opts.viewInfo.renderControlCharacters), // DISABLED WHEN SCREEN READER IS ATTACHED - fontLigatures: (accessibilityIsOn ? false : opts.viewInfo.fontLigatures), // DISABLED WHEN SCREEN READER IS ATTACHED + fontLigatures: opts.viewInfo.fontLigatures, renderIndentGuides: (accessibilityIsOn ? false : opts.viewInfo.renderIndentGuides), // DISABLED WHEN SCREEN READER IS ATTACHED highlightActiveIndentGuide: opts.viewInfo.highlightActiveIndentGuide, renderLineHighlight: opts.viewInfo.renderLineHighlight, @@ -2644,6 +2653,7 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = { ariaLabel: nls.localize('editorViewAccessibleLabel', "Editor content"), renderLineNumbers: RenderLineNumbersType.On, renderCustomLineNumbers: null, + cursorSurroundingLines: 0, renderFinalNewline: true, selectOnLineNumbers: true, glyphMargin: true, diff --git a/src/vs/editor/common/controller/cursorTypeOperations.ts b/src/vs/editor/common/controller/cursorTypeOperations.ts index 430e227942..abcfd19c64 100644 --- a/src/vs/editor/common/controller/cursorTypeOperations.ts +++ b/src/vs/editor/common/controller/cursorTypeOperations.ts @@ -437,8 +437,6 @@ export class TypeOperations { return false; } - const isEqualPair = (ch === config.autoClosingPairsClose[ch]); - for (let i = 0, len = selections.length; i < len; i++) { const selection = selections[i]; @@ -454,14 +452,6 @@ export class TypeOperations { return false; } - if (isEqualPair) { - const lineTextBeforeCursor = lineText.substr(0, position.column - 1); - const chCntBefore = this._countNeedlesInHaystack(lineTextBeforeCursor, ch); - if (chCntBefore % 2 === 0) { - return false; - } - } - // Must over-type a closing character typed by the editor let found = false; for (let j = 0, lenJ = autoClosedCharacters.length; j < lenJ; j++) { @@ -947,6 +937,7 @@ export class TypeWithAutoClosingCommand extends ReplaceCommandWithOffsetCursorSt super(selection, openCharacter + closeCharacter, 0, -closeCharacter.length); this._closeCharacter = closeCharacter; this.closeCharacterRange = null; + this.enclosingRange = null; } public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { diff --git a/src/vs/editor/common/controller/oneCursor.ts b/src/vs/editor/common/controller/oneCursor.ts index baf445e4c2..7412617806 100644 --- a/src/vs/editor/common/controller/oneCursor.ts +++ b/src/vs/editor/common/controller/oneCursor.ts @@ -11,8 +11,8 @@ import { TrackedRangeStickiness } from 'vs/editor/common/model'; export class OneCursor { - public modelState: SingleCursorState; - public viewState: SingleCursorState; + public modelState!: SingleCursorState; + public viewState!: SingleCursorState; private _selTrackedRange: string | null; private _trackSelection: boolean; diff --git a/src/vs/editor/common/diff/diffComputer.ts b/src/vs/editor/common/diff/diffComputer.ts index d374847679..9d606a1e90 100644 --- a/src/vs/editor/common/diff/diffComputer.ts +++ b/src/vs/editor/common/diff/diffComputer.ts @@ -328,6 +328,8 @@ export class DiffComputer { this.modifiedLines = modifiedLines; this.original = new LineMarkerSequence(originalLines); this.modified = new LineMarkerSequence(modifiedLines); + + this.computationStartTime = (new Date()).getTime(); } public computeDiff(): ILineChange[] { diff --git a/src/vs/editor/common/model/indentationGuesser.ts b/src/vs/editor/common/model/indentationGuesser.ts index 884d657d70..9561ebf108 100644 --- a/src/vs/editor/common/model/indentationGuesser.ts +++ b/src/vs/editor/common/model/indentationGuesser.ts @@ -7,8 +7,8 @@ import { CharCode } from 'vs/base/common/charCode'; import { ITextBuffer } from 'vs/editor/common/model'; class SpacesDiffResult { - public spacesDiff: number; - public looksLikeAlignment: boolean; + public spacesDiff: number = 0; + public looksLikeAlignment: boolean = false; } /** @@ -158,8 +158,19 @@ export function guessIndentation(source: ITextBuffer, defaultTabSize: number, de spacesDiff(previousLineText, previousLineIndentation, currentLineText, currentLineIndentation, tmp); if (tmp.looksLikeAlignment) { - // skip this line entirely - continue; + // if defaultInsertSpaces === true && the spaces count == tabSize, we may want to count it as valid indentation + // + // - item1 + // - item2 + // + // otherwise skip this line entirely + // + // const a = 1, + // b = 2; + + if (!(defaultInsertSpaces && defaultTabSize === tmp.spacesDiff)) { + continue; + } } let currentSpacesDiff = tmp.spacesDiff; diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts index 59976733d2..525b5565ca 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts @@ -265,16 +265,16 @@ class PieceTreeSearchCache { } export class PieceTreeBase { - root: TreeNode; - protected _buffers: StringBuffer[]; // 0 is change buffer, others are readonly original buffer. - protected _lineCnt: number; - protected _length: number; - protected _EOL: string; - protected _EOLLength: number; - protected _EOLNormalized: boolean; - private _lastChangeBufferPos: BufferCursor; - private _searchCache: PieceTreeSearchCache; - private _lastVisitedLine: { lineNumber: number; value: string; }; + root!: TreeNode; + protected _buffers!: StringBuffer[]; // 0 is change buffer, others are readonly original buffer. + protected _lineCnt!: number; + protected _length!: number; + protected _EOL!: string; + protected _EOLLength!: number; + protected _EOLNormalized!: boolean; + private _lastChangeBufferPos!: BufferCursor; + private _searchCache!: PieceTreeSearchCache; + private _lastVisitedLine!: { lineNumber: number; value: string; }; constructor(chunks: StringBuffer[], eol: '\r\n' | '\n', eolNormalized: boolean) { this.create(chunks, eol, eolNormalized); diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 94cb6ccef6..c070748b1e 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -329,7 +329,9 @@ export class TextModel extends Disposable implements model.ITextModel { this._isTooLargeForSyncing = (bufferTextLength > TextModel.MODEL_SYNC_LIMIT); - this._setVersionId(1); + this._versionId = 1; + this._alternativeVersionId = 1; + this._isDisposed = false; this._isDisposing = false; @@ -693,11 +695,7 @@ export class TextModel extends Disposable implements model.ITextModel { } private _increaseVersionId(): void { - this._setVersionId(this._versionId + 1); - } - - private _setVersionId(newVersionId: number): void { - this._versionId = newVersionId; + this._versionId = this._versionId + 1; this._alternativeVersionId = this._versionId; } diff --git a/src/vs/editor/common/model/textModelTokens.ts b/src/vs/editor/common/model/textModelTokens.ts index 99153794ef..f9eb39cc13 100644 --- a/src/vs/editor/common/model/textModelTokens.ts +++ b/src/vs/editor/common/model/textModelTokens.ts @@ -28,7 +28,10 @@ export class TokenizationStateStore { private _invalidLineStartIndex: number; constructor() { - this._reset(null); + this._beginState = []; + this._valid = []; + this._len = 0; + this._invalidLineStartIndex = 0; } private _reset(initialState: IState | null): void { diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 52177b7fd8..1d504f28f7 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -1288,7 +1288,7 @@ export interface CommentThread { threadId: string; resource: string | null; range: IRange; - label: string; + label: string | undefined; contextValue: string | undefined; comments: Comment[] | undefined; onDidChangeComments: Event; @@ -1296,7 +1296,7 @@ export interface CommentThread { input?: CommentInput; onDidChangeInput: Event; onDidChangeRange: Event; - onDidChangeLabel: Event; + onDidChangeLabel: Event; onDidChangeCollasibleState: Event; isDisposed: boolean; } diff --git a/src/vs/editor/common/modes/languageConfigurationRegistry.ts b/src/vs/editor/common/modes/languageConfigurationRegistry.ts index 9d83292d32..98c3832417 100644 --- a/src/vs/editor/common/modes/languageConfigurationRegistry.ts +++ b/src/vs/editor/common/modes/languageConfigurationRegistry.ts @@ -53,7 +53,7 @@ export class RichEditSupport { public readonly characterPair: CharacterPairSupport; public readonly wordDefinition: RegExp; public readonly onEnter: OnEnterSupport | null; - public readonly indentRulesSupport: IndentRulesSupport; + public readonly indentRulesSupport: IndentRulesSupport | null; public readonly indentationRules: IndentationRule | undefined; public readonly foldingRules: FoldingRules; @@ -81,6 +81,8 @@ export class RichEditSupport { this.indentationRules = this._conf.indentationRules; if (this._conf.indentationRules) { this.indentRulesSupport = new IndentRulesSupport(this._conf.indentationRules); + } else { + this.indentRulesSupport = null; } this.foldingRules = this._conf.folding || {}; @@ -170,7 +172,9 @@ export class RichEditSupport { } export class LanguageConfigurationChangeEvent { - languageIdentifier: LanguageIdentifier; + constructor( + public readonly languageIdentifier: LanguageIdentifier + ) { } } export class LanguageConfigurationRegistryImpl { @@ -184,11 +188,11 @@ export class LanguageConfigurationRegistryImpl { let previous = this._getRichEditSupport(languageIdentifier.id); let current = new RichEditSupport(languageIdentifier, previous, configuration); this._entries.set(languageIdentifier.id, current); - this._onDidChange.fire({ languageIdentifier }); + this._onDidChange.fire(new LanguageConfigurationChangeEvent(languageIdentifier)); return toDisposable(() => { if (this._entries.get(languageIdentifier.id) === current) { this._entries.set(languageIdentifier.id, previous); - this._onDidChange.fire({ languageIdentifier }); + this._onDidChange.fire(new LanguageConfigurationChangeEvent(languageIdentifier)); } }); } diff --git a/src/vs/editor/common/modes/textToHtmlTokenizer.ts b/src/vs/editor/common/modes/textToHtmlTokenizer.ts index 6cea903b10..25dc1a43b6 100644 --- a/src/vs/editor/common/modes/textToHtmlTokenizer.ts +++ b/src/vs/editor/common/modes/textToHtmlTokenizer.ts @@ -77,6 +77,10 @@ export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens partContent += '​'; break; + case CharCode.Space: + partContent += ' '; + break; + default: partContent += String.fromCharCode(charCode); } diff --git a/src/vs/editor/common/services/editorWorkerServiceImpl.ts b/src/vs/editor/common/services/editorWorkerServiceImpl.ts index 2f26f50b9d..30192baec2 100644 --- a/src/vs/editor/common/services/editorWorkerServiceImpl.ts +++ b/src/vs/editor/common/services/editorWorkerServiceImpl.ts @@ -167,6 +167,7 @@ class WorkerManager extends Disposable { super(); this._modelService = modelService; this._editorWorkerClient = null; + this._lastWorkerUsedTime = (new Date()).getTime(); let stopWorkerInterval = this._register(new IntervalTimer()); stopWorkerInterval.cancelAndSet(() => this._checkStopIdleWorker(), Math.round(STOP_WORKER_DELTA_TIME_MS / 2)); diff --git a/src/vs/editor/common/view/minimapCharRenderer.ts b/src/vs/editor/common/view/minimapCharRenderer.ts index 751bcf1404..fc87a3473d 100644 --- a/src/vs/editor/common/view/minimapCharRenderer.ts +++ b/src/vs/editor/common/view/minimapCharRenderer.ts @@ -16,8 +16,8 @@ export class MinimapTokensColorTracker { return this._INSTANCE; } - private _colors: RGBA8[]; - private _backgroundIsLight: boolean; + private _colors!: RGBA8[]; + private _backgroundIsLight!: boolean; private _onDidChange = new Emitter(); public readonly onDidChange: Event = this._onDidChange.event; diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index ae09f749cb..591ae35c19 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -136,6 +136,13 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor let charCodeIsTab = (charCode === CharCode.Tab); let charCodeClass = classifier.get(charCode); + if (strings.isLowSurrogate(charCode)/* && i + 1 < len */) { + // A surrogate pair must always be considered as a single unit, so it is never to be broken + // => advance visibleColumn by 1 and advance to next char code... + visibleColumn = visibleColumn + 1; + continue; + } + if (charCodeClass === CharacterClass.BREAK_BEFORE) { // This is a character that indicates that a break should happen before it // Since we are certain the character before `i` fits, there's no extra checking needed, diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 942a74829b..e96895197d 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -155,13 +155,13 @@ export class SplitLinesCollection implements IViewModelLinesCollection { private columnsForFullWidthChar: number; private wrappingIndent: WrappingIndent; private tabSize: number; - private lines: ISplitLine[]; + private lines!: ISplitLine[]; - private prefixSumComputer: PrefixSumComputerWithCache; + private prefixSumComputer!: PrefixSumComputerWithCache; private readonly linePositionMapperFactory: ILineMapperFactory; - private hiddenAreasIds: string[]; + private hiddenAreasIds!: string[]; constructor(model: ITextModel, linePositionMapperFactory: ILineMapperFactory, tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent) { this.model = model; diff --git a/src/vs/editor/common/viewModel/viewModelDecorations.ts b/src/vs/editor/common/viewModel/viewModelDecorations.ts index d6b24205f8..cc1cf1a573 100644 --- a/src/vs/editor/common/viewModel/viewModelDecorations.ts +++ b/src/vs/editor/common/viewModel/viewModelDecorations.ts @@ -42,7 +42,8 @@ export class ViewModelDecorations implements IDisposable { this._linesCollection = linesCollection; this._coordinatesConverter = coordinatesConverter; this._decorationsCache = Object.create(null); - this._clearCachedModelDecorationsResolver(); + this._cachedModelDecorationsResolver = null; + this._cachedModelDecorationsResolverViewRange = null; } private _clearCachedModelDecorationsResolver(): void { diff --git a/src/vs/editor/contrib/caretOperations/moveCaretCommand.ts b/src/vs/editor/contrib/caretOperations/moveCaretCommand.ts index 8bf28d190b..59e7c05a0d 100644 --- a/src/vs/editor/contrib/caretOperations/moveCaretCommand.ts +++ b/src/vs/editor/contrib/caretOperations/moveCaretCommand.ts @@ -17,11 +17,15 @@ export class MoveCaretCommand implements ICommand { private _cutEndIndex: number; private _moved: boolean; - private _selectionId: string; + private _selectionId: string | null; constructor(selection: Selection, isMovingLeft: boolean) { this._selection = selection; this._isMovingLeft = isMovingLeft; + this._cutStartIndex = -1; + this._cutEndIndex = -1; + this._moved = false; + this._selectionId = null; } public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void { @@ -64,7 +68,7 @@ export class MoveCaretCommand implements ICommand { } public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { - let result = helper.getTrackedSelection(this._selectionId); + let result = helper.getTrackedSelection(this._selectionId!); if (this._moved) { result = result.setStartPosition(result.startLineNumber, this._cutStartIndex); result = result.setEndPosition(result.startLineNumber, this._cutEndIndex); diff --git a/src/vs/editor/contrib/codeAction/codeAction.ts b/src/vs/editor/contrib/codeAction/codeAction.ts index 8a9335996d..567667d38d 100644 --- a/src/vs/editor/contrib/codeAction/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/codeAction.ts @@ -117,8 +117,8 @@ function getCodeActionProviders( } registerLanguageCommand('_executeCodeActionProvider', async function (accessor, args): Promise> { - const { resource, range, kind } = args; - if (!(resource instanceof URI) || !Range.isIRange(range)) { + const { resource, rangeOrSelection, kind } = args; + if (!(resource instanceof URI)) { throw illegalArgument(); } @@ -127,9 +127,19 @@ registerLanguageCommand('_executeCodeActionProvider', async function (accessor, throw illegalArgument(); } + const validatedRangeOrSelection = Selection.isISelection(rangeOrSelection) + ? Selection.liftSelection(rangeOrSelection) + : Range.isIRange(rangeOrSelection) + ? model.validateRange(rangeOrSelection) + : undefined; + + if (!validatedRangeOrSelection) { + throw illegalArgument(); + } + const codeActionSet = await getCodeActions( model, - model.validateRange(range), + validatedRangeOrSelection, { type: 'manual', filter: { includeSourceActions: true, kind: kind && kind.value ? new CodeActionKind(kind.value) : undefined } }, CancellationToken.None); diff --git a/src/vs/editor/contrib/codeAction/codeActionWidget.ts b/src/vs/editor/contrib/codeAction/codeActionWidget.ts index d16aac5244..8a060c19a9 100644 --- a/src/vs/editor/contrib/codeAction/codeActionWidget.ts +++ b/src/vs/editor/contrib/codeAction/codeActionWidget.ts @@ -30,6 +30,7 @@ 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/test/codeAction.test.ts b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts index 3ed337306f..07c0b11d41 100644 --- a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts @@ -59,8 +59,8 @@ suite('CodeAction', () => { command: { abc: { command: new class implements modes.Command { - id: '1'; - title: 'abc'; + id!: '1'; + title!: 'abc'; }, title: 'Extract to inner function in function "test"' } @@ -69,7 +69,7 @@ suite('CodeAction', () => { bcd: { diagnostics: [], edit: new class implements modes.WorkspaceEdit { - edits: modes.ResourceTextEdit[]; + edits!: modes.ResourceTextEdit[]; }, title: 'abc' } diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index 551619ec0d..66e3011e2b 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -32,7 +32,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { private _currentCodeLensModel: CodeLensModel | undefined; private _modelChangeCounter: number = 0; private _currentResolveCodeLensSymbolsPromise: CancelablePromise | undefined; - private _detectVisibleLenses: RunOnceScheduler; + private _detectVisibleLenses!: RunOnceScheduler; constructor( private readonly _editor: editorBrowser.ICodeEditor, diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index 2f85d841a0..d12738caf7 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -60,7 +60,7 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { private readonly _editor: editorBrowser.ICodeEditor; private readonly _commands = new Map(); - private _widgetPosition: editorBrowser.IContentWidgetPosition; + private _widgetPosition?: editorBrowser.IContentWidgetPosition; constructor( editor: editorBrowser.ICodeEditor, @@ -147,8 +147,8 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { }; } - getPosition(): editorBrowser.IContentWidgetPosition { - return this._widgetPosition; + getPosition(): editorBrowser.IContentWidgetPosition | null { + return this._widgetPosition || null; } isVisible(): boolean { @@ -192,9 +192,9 @@ export class CodeLensHelper { export class CodeLensWidget { private readonly _editor: editorBrowser.ICodeEditor; - private readonly _viewZone: CodeLensViewZone; - private readonly _viewZoneId: number; - private readonly _contentWidget: CodeLensContentWidget; + private readonly _viewZone!: CodeLensViewZone; + private readonly _viewZoneId!: number; + private readonly _contentWidget!: CodeLensContentWidget; private _decorationIds: string[]; private _data: CodeLensItem[]; diff --git a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts index 63a180e483..bd9e5f7a32 100644 --- a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts @@ -123,8 +123,8 @@ class SaturationBox extends Disposable { private readonly domNode: HTMLElement; private readonly selection: HTMLElement; private readonly canvas: HTMLCanvasElement; - private width: number; - private height: number; + private width!: number; + private height!: number; private monitor: GlobalMouseMoveMonitor | null; private _onDidChange = new Emitter<{ s: number, v: number }>(); @@ -235,7 +235,7 @@ abstract class Strip extends Disposable { protected domNode: HTMLElement; protected overlay: HTMLElement; protected slider: HTMLElement; - private height: number; + private height!: number; private _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; diff --git a/src/vs/editor/contrib/comment/lineCommentCommand.ts b/src/vs/editor/contrib/comment/lineCommentCommand.ts index 4f744095f6..6e3273b7e8 100644 --- a/src/vs/editor/contrib/comment/lineCommentCommand.ts +++ b/src/vs/editor/contrib/comment/lineCommentCommand.ts @@ -49,7 +49,7 @@ export const enum Type { export class LineCommentCommand implements editorCommon.ICommand { private readonly _selection: Selection; - private _selectionId: string; + private _selectionId: string | null; private _deltaColumn: number; private _moveEndPositionDown: boolean; private readonly _tabSize: number; @@ -57,9 +57,11 @@ export class LineCommentCommand implements editorCommon.ICommand { constructor(selection: Selection, tabSize: number, type: Type) { this._selection = selection; + this._selectionId = null; this._tabSize = tabSize; this._type = type; this._deltaColumn = 0; + this._moveEndPositionDown = false; } /** @@ -323,7 +325,7 @@ export class LineCommentCommand implements editorCommon.ICommand { } public computeCursorState(model: ITextModel, helper: editorCommon.ICursorStateComputerData): Selection { - let result = helper.getTrackedSelection(this._selectionId); + let result = helper.getTrackedSelection(this._selectionId!); if (this._moveEndPositionDown) { result = result.setEndPosition(result.endLineNumber + 1, 1); diff --git a/src/vs/editor/contrib/dnd/dragAndDropCommand.ts b/src/vs/editor/contrib/dnd/dragAndDropCommand.ts index b18f5bf0cc..cbb8da1531 100644 --- a/src/vs/editor/contrib/dnd/dragAndDropCommand.ts +++ b/src/vs/editor/contrib/dnd/dragAndDropCommand.ts @@ -14,13 +14,14 @@ export class DragAndDropCommand implements editorCommon.ICommand { private readonly selection: Selection; private readonly targetPosition: Position; - private targetSelection: Selection; + private targetSelection: Selection | null; private readonly copy: boolean; constructor(selection: Selection, targetPosition: Position, copy: boolean) { this.selection = selection; this.targetPosition = targetPosition; this.copy = copy; + this.targetSelection = null; } public getEditOperations(model: ITextModel, builder: editorCommon.IEditOperationBuilder): void { @@ -102,6 +103,6 @@ export class DragAndDropCommand implements editorCommon.ICommand { } public computeCursorState(model: ITextModel, helper: editorCommon.ICursorStateComputerData): Selection { - return this.targetSelection; + return this.targetSelection!; } } diff --git a/src/vs/editor/contrib/documentSymbols/outlineTree.ts b/src/vs/editor/contrib/documentSymbols/outlineTree.ts index cdc98bf098..4a35ac08d4 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineTree.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineTree.ts @@ -45,16 +45,19 @@ export class OutlineIdentityProvider implements IIdentityProvider { export class OutlineGroupTemplate { static id = 'OutlineGroupTemplate'; - - labelContainer: HTMLElement; - label: HighlightedLabel; + constructor( + readonly labelContainer: HTMLElement, + readonly label: HighlightedLabel, + ) { } } export class OutlineElementTemplate { static id = 'OutlineElementTemplate'; - container: HTMLElement; - iconLabel: IconLabel; - decoration: HTMLElement; + constructor( + readonly container: HTMLElement, + readonly iconLabel: IconLabel, + readonly decoration: HTMLElement, + ) { } } export class OutlineVirtualDelegate implements IListVirtualDelegate { @@ -80,7 +83,7 @@ export class OutlineGroupRenderer implements ITreeRenderer, index: number, template: OutlineGroupTemplate): void { @@ -109,7 +112,7 @@ export class OutlineElementRenderer implements ITreeRenderer, index: number, template: OutlineElementTemplate): void { diff --git a/src/vs/editor/contrib/find/findController.ts b/src/vs/editor/contrib/find/findController.ts index e5c7058385..f34e82f441 100644 --- a/src/vs/editor/contrib/find/findController.ts +++ b/src/vs/editor/contrib/find/findController.ts @@ -374,8 +374,8 @@ export class CommonFindController extends Disposable implements editorCommon.IEd export class FindController extends CommonFindController implements IFindController { - private _widget: FindWidget; - private _findOptionsWidget: FindOptionsWidget; + private _widget: FindWidget | null; + private _findOptionsWidget: FindOptionsWidget | null; constructor( editor: ICodeEditor, @@ -387,6 +387,8 @@ export class FindController extends CommonFindController implements IFindControl @optional(IClipboardService) clipboardService: IClipboardService ) { super(editor, _contextKeyService, storageService, clipboardService); + this._widget = null; + this._findOptionsWidget = null; } protected _start(opts: IFindStartOptions): void { @@ -394,7 +396,7 @@ export class FindController extends CommonFindController implements IFindControl this._createFindWidget(); } - if (!this._widget.getPosition() && this._editor.getConfiguration().contribInfo.find.autoFindInSelection) { + if (!this._widget!.getPosition() && this._editor.getConfiguration().contribInfo.find.autoFindInSelection) { // not visible yet so we need to set search scope if `editor.find.autoFindInSelection` is `true` opts.updateSearchScope = true; } @@ -402,9 +404,9 @@ export class FindController extends CommonFindController implements IFindControl super._start(opts); if (opts.shouldFocus === FindStartFocusAction.FocusReplaceInput) { - this._widget.focusReplaceInput(); + this._widget!.focusReplaceInput(); } else if (opts.shouldFocus === FindStartFocusAction.FocusFindInput) { - this._widget.focusFindInput(); + this._widget!.focusFindInput(); } } @@ -413,9 +415,9 @@ export class FindController extends CommonFindController implements IFindControl this._createFindWidget(); } if (this._state.isRevealed) { - this._widget.highlightFindOptions(); + this._widget!.highlightFindOptions(); } else { - this._findOptionsWidget.highlightFindOptions(); + this._findOptionsWidget!.highlightFindOptions(); } } diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index 29c8bfe6f5..553f43a9de 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -93,19 +93,19 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas private readonly _keybindingService: IKeybindingService; private readonly _contextKeyService: IContextKeyService; - private _domNode: HTMLElement; - private _findInput: FindInput; - private _replaceInputBox: HistoryInputBox; + private _domNode!: HTMLElement; + private _findInput!: FindInput; + private _replaceInputBox!: HistoryInputBox; - private _toggleReplaceBtn: SimpleButton; - private _matchesCount: HTMLElement; - private _prevBtn: SimpleButton; - private _nextBtn: SimpleButton; - private _toggleSelectionFind: SimpleCheckbox; - private _closeBtn: SimpleButton; - private _preserveCase: Checkbox; - private _replaceBtn: SimpleButton; - private _replaceAllBtn: SimpleButton; + private _toggleReplaceBtn!: SimpleButton; + private _matchesCount!: HTMLElement; + private _prevBtn!: SimpleButton; + private _nextBtn!: SimpleButton; + private _toggleSelectionFind!: SimpleCheckbox; + private _closeBtn!: SimpleButton; + private _preserveCase!: Checkbox; + private _replaceBtn!: SimpleButton; + private _replaceAllBtn!: SimpleButton; private _isVisible: boolean; private _isReplaceVisible: boolean; @@ -118,8 +118,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas private _viewZone?: FindWidgetViewZone; private _viewZoneId?: number; - private _resizeSash: Sash; - private _resized: boolean; + private _resizeSash!: Sash; + private _resized!: boolean; private readonly _updateHistoryDelayer: Delayer; constructor( @@ -784,7 +784,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas return ` (${kb.getLabel()})`; } - private _buildFindPart(): HTMLElement { + private _buildDomNode(): void { // Find input this._findInput = this._register(new ContextScopedFindInput(null, this._contextViewProvider, { width: FIND_INPUT_AREA_WIDTH, @@ -908,10 +908,6 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas findPart.appendChild(this._closeBtn.domNode); - return findPart; - } - - private _buildReplacePart(): HTMLElement { // Replace input let replaceInput = document.createElement('div'); replaceInput.className = 'replace-input'; @@ -977,16 +973,6 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas replacePart.appendChild(this._replaceBtn.domNode); replacePart.appendChild(this._replaceAllBtn.domNode); - return replacePart; - } - - private _buildDomNode(): void { - // Find part - let findPart = this._buildFindPart(); - - // Replace part - let replacePart = this._buildReplacePart(); - // Toggle replace button this._toggleReplaceBtn = this._register(new SimpleButton({ label: NLS_TOGGLE_REPLACE_MODE_BTN_LABEL, @@ -1014,10 +1000,6 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._domNode.appendChild(findPart); this._domNode.appendChild(replacePart); - this._buildSash(); - } - - private _buildSash() { this._resizeSash = new Sash(this._domNode, this, { orientation: Orientation.VERTICAL }); this._resized = false; let originalWidth = FIND_WIDGET_INITIAL_WIDTH; diff --git a/src/vs/editor/contrib/find/replaceAllCommand.ts b/src/vs/editor/contrib/find/replaceAllCommand.ts index 85367510d0..ec8bc4dca4 100644 --- a/src/vs/editor/contrib/find/replaceAllCommand.ts +++ b/src/vs/editor/contrib/find/replaceAllCommand.ts @@ -16,7 +16,7 @@ interface IEditOperation { export class ReplaceAllCommand implements editorCommon.ICommand { private readonly _editorSelection: Selection; - private _trackedEditorSelectionId: string; + private _trackedEditorSelectionId: string | null; private readonly _ranges: Range[]; private readonly _replaceStrings: string[]; @@ -24,6 +24,7 @@ export class ReplaceAllCommand implements editorCommon.ICommand { this._editorSelection = editorSelection; this._ranges = ranges; this._replaceStrings = replaceStrings; + this._trackedEditorSelectionId = null; } public getEditOperations(model: ITextModel, builder: editorCommon.IEditOperationBuilder): void { @@ -66,6 +67,6 @@ export class ReplaceAllCommand implements editorCommon.ICommand { } public computeCursorState(model: ITextModel, helper: editorCommon.ICursorStateComputerData): Selection { - return helper.getTrackedSelection(this._trackedEditorSelectionId); + return helper.getTrackedSelection(this._trackedEditorSelectionId!); } } diff --git a/src/vs/editor/contrib/find/test/findController.test.ts b/src/vs/editor/contrib/find/test/findController.test.ts index 87a07bea5c..0bb7611485 100644 --- a/src/vs/editor/contrib/find/test/findController.test.ts +++ b/src/vs/editor/contrib/find/test/findController.test.ts @@ -24,7 +24,6 @@ export class TestFindController extends CommonFindController { public hasFocus: boolean; public delayUpdateHistory: boolean = false; - public delayedUpdateHistoryPromise: Promise; private _findInputFocused: IContextKey; @@ -37,6 +36,7 @@ export class TestFindController extends CommonFindController { super(editor, contextKeyService, storageService, clipboardService); this._findInputFocused = CONTEXT_FIND_INPUT_FOCUSED.bindTo(contextKeyService); this._updateHistoryDelayer = new Delayer(50); + this.hasFocus = false; } protected _start(opts: IFindStartOptions): void { diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index db1b60fe85..1961ef18ae 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -79,6 +79,7 @@ export class FoldingController extends Disposable implements IEditorContribution private cursorChangedScheduler: RunOnceScheduler | null; private readonly localToDispose = this._register(new DisposableStore()); + private mouseDownInfo: { lineNumber: number, iconClicked: boolean } | null; constructor( editor: ICodeEditor, @@ -90,6 +91,15 @@ export class FoldingController extends Disposable implements IEditorContribution this._autoHideFoldingControls = this.editor.getConfiguration().contribInfo.showFoldingControls === 'mouseover'; this._useFoldingProviders = this.editor.getConfiguration().contribInfo.foldingStrategy !== 'indentation'; + this.foldingModel = null; + this.hiddenRangeModel = null; + this.rangeProvider = null; + this.foldingRegionPromise = null; + this.foldingStateMemento = null; + this.foldingModelPromise = null; + this.updateScheduler = null; + this.cursorChangedScheduler = null; + this.mouseDownInfo = null; this.foldingDecorationProvider = new FoldingDecorationProvider(editor); this.foldingDecorationProvider.autoHideFoldingControls = this._autoHideFoldingControls; @@ -331,8 +341,6 @@ export class FoldingController extends Disposable implements IEditorContribution } - private mouseDownInfo: { lineNumber: number, iconClicked: boolean } | null; - private onEditorMouseDown(e: IEditorMouseEvent): void { this.mouseDownInfo = null; diff --git a/src/vs/editor/contrib/folding/foldingRanges.ts b/src/vs/editor/contrib/folding/foldingRanges.ts index b1ab0220fd..f6a6141ad8 100644 --- a/src/vs/editor/contrib/folding/foldingRanges.ts +++ b/src/vs/editor/contrib/folding/foldingRanges.ts @@ -28,6 +28,7 @@ export class FoldingRegions { this._endIndexes = endIndexes; this._collapseStates = new Uint32Array(Math.ceil(startIndexes.length / 32)); this._types = types; + this._parentsComputed = false; } private ensureParentIndices() { @@ -186,4 +187,4 @@ export class FoldingRegion { hidesLine(lineNumber: number) { return this.startLineNumber < lineNumber && lineNumber <= this.endLineNumber; } -} \ No newline at end of file +} diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts b/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts index 2d522a05a6..3a8499aea9 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts +++ b/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts @@ -34,18 +34,16 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC private readonly editor: ICodeEditor; private readonly toUnhook = new DisposableStore(); - private decorations: string[]; - private currentWordUnderMouse: IWordAtPosition | null; - private previousPromise: CancelablePromise | null; + private decorations: string[] = []; + private currentWordUnderMouse: IWordAtPosition | null = null; + private previousPromise: CancelablePromise | null = null; constructor( editor: ICodeEditor, @ITextModelService private readonly textModelResolverService: ITextModelService, @IModeService private readonly modeService: IModeService ) { - this.decorations = []; this.editor = editor; - this.previousPromise = null; let linkGesture = new ClickLinkGesture(editor); this.toUnhook.add(linkGesture); diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index 897e5cfd4c..0dde37e8e2 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -165,15 +165,15 @@ class MessageWidget { export class MarkerNavigationWidget extends PeekViewWidget { - private _parentContainer: HTMLElement; - private _container: HTMLElement; - private _icon: HTMLElement; - private _message: MessageWidget; + private _parentContainer!: HTMLElement; + private _container!: HTMLElement; + private _icon!: HTMLElement; + private _message!: MessageWidget; private readonly _callOnDispose = new DisposableStore(); private _severity: MarkerSeverity; private _backgroundColor?: Color; private _onDidSelectRelatedInformation = new Emitter(); - private _heightInPixel: number; + private _heightInPixel!: number; readonly onDidSelectRelatedInformation: Event = this._onDidSelectRelatedInformation.event; @@ -228,7 +228,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { protected _fillHead(container: HTMLElement): void { super._fillHead(container); - this._actionbarWidget.push(this.actions, { label: false, icon: true }); + this._actionbarWidget!.push(this.actions, { label: false, icon: true }); } protected _fillTitleIcon(container: HTMLElement): void { diff --git a/src/vs/editor/contrib/hover/hover.css b/src/vs/editor/contrib/hover/hover.css index a11ede8bf1..26daad5c4e 100644 --- a/src/vs/editor/contrib/hover/hover.css +++ b/src/vs/editor/contrib/hover/hover.css @@ -84,13 +84,14 @@ .monaco-editor-hover .hover-row.status-bar .actions { display: flex; + padding: 0px 8px; } .monaco-editor-hover .hover-row.status-bar .actions .action-container { - margin: 0px 8px; + margin-right: 16px; cursor: pointer; } .monaco-editor-hover .hover-row.status-bar .actions .action-container .action .icon { padding-right: 4px; -} \ No newline at end of file +} diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index 30865a0884..b553c9e551 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -34,27 +34,27 @@ export class ModesHoverController implements IEditorContribution { private readonly _toUnhook = new DisposableStore(); private readonly _didChangeConfigurationHandler: IDisposable; - private _contentWidget: ModesContentHoverWidget; - private _glyphWidget: ModesGlyphHoverWidget; + private _contentWidget: ModesContentHoverWidget | null; + private _glyphWidget: ModesGlyphHoverWidget | null; get contentWidget(): ModesContentHoverWidget { if (!this._contentWidget) { - this._createHoverWidget(); + this._createHoverWidgets(); } - return this._contentWidget; + return this._contentWidget!; } get glyphWidget(): ModesGlyphHoverWidget { if (!this._glyphWidget) { - this._createHoverWidget(); + this._createHoverWidgets(); } - return this._glyphWidget; + return this._glyphWidget!; } private _isMouseDown: boolean; private _hoverClicked: boolean; - private _isHoverEnabled: boolean; - private _isHoverSticky: boolean; + private _isHoverEnabled!: boolean; + private _isHoverSticky!: boolean; static get(editor: ICodeEditor): ModesHoverController { return editor.getContribution(ModesHoverController.ID); @@ -69,6 +69,8 @@ export class ModesHoverController implements IEditorContribution { ) { this._isMouseDown = false; this._hoverClicked = false; + this._contentWidget = null; + this._glyphWidget = null; this._hookEvents(); @@ -196,15 +198,15 @@ export class ModesHoverController implements IEditorContribution { } private _hideWidgets(): void { - if (!this._contentWidget || (this._isMouseDown && this._hoverClicked && this._contentWidget.isColorPickerVisible())) { + if (!this._glyphWidget || !this._contentWidget || (this._isMouseDown && this._hoverClicked && this._contentWidget.isColorPickerVisible())) { return; } - this._glyphWidget.hide(); + this._glyphWidget!.hide(); this._contentWidget.hide(); } - private _createHoverWidget() { + private _createHoverWidgets() { this._contentWidget = new ModesContentHoverWidget(this._editor, this._markerDecorationsService, this._themeService, this._keybindingService, this._modeService, this._openerService); this._glyphWidget = new ModesGlyphHoverWidget(this._editor, this._modeService, this._openerService); } diff --git a/src/vs/editor/contrib/hover/hoverWidgets.ts b/src/vs/editor/contrib/hover/hoverWidgets.ts index 7ff6243bce..b8880c62fe 100644 --- a/src/vs/editor/contrib/hover/hoverWidgets.ts +++ b/src/vs/editor/contrib/hover/hoverWidgets.ts @@ -42,6 +42,7 @@ export class ContentHoverWidget extends Widget implements editorBrowser.IContent this._id = id; this._editor = editor; this._isVisible = false; + this._stoleFocus = false; this._containerDomNode = document.createElement('div'); this._containerDomNode.className = 'monaco-editor-hover hidden'; @@ -72,6 +73,7 @@ export class ContentHoverWidget extends Widget implements editorBrowser.IContent this._editor.addContentWidget(this); this._showAtPosition = null; this._showAtRange = null; + this._stoleFocus = false; } public getId(): string { diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 08412d5b90..a7a6e2a7de 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -70,6 +70,7 @@ class ModesContentComputer implements IHoverComputer { private readonly _markerDecorationsService: IMarkerDecorationsService ) { this._editor = editor; + this._result = []; } setRange(range: Range): void { @@ -213,6 +214,8 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this._computer = new ModesContentComputer(this._editor, markerDecorationsService); this._highlightDecorations = []; this._isChangingDecorations = false; + this._shouldFocus = false; + this._colorPicker = null; this._hoverOperation = new HoverOperation( this._computer, diff --git a/src/vs/editor/contrib/hover/modesGlyphHover.ts b/src/vs/editor/contrib/hover/modesGlyphHover.ts index f2d8968281..2975d685b2 100644 --- a/src/vs/editor/contrib/hover/modesGlyphHover.ts +++ b/src/vs/editor/contrib/hover/modesGlyphHover.ts @@ -27,6 +27,7 @@ class MarginComputer implements IHoverComputer { constructor(editor: ICodeEditor) { this._editor = editor; this._lineNumber = -1; + this._result = []; } public setLineNumber(lineNumber: number): void { @@ -100,6 +101,7 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget { ) { super(ModesGlyphHoverWidget.ID, editor); + this._messages = []; this._lastLineNumber = -1; this._markdownRenderer = this._register(new MarkdownRenderer(this._editor, modeService, openerService)); diff --git a/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts b/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts index 6a854e8030..1f2520da13 100644 --- a/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts +++ b/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts @@ -37,8 +37,8 @@ class InPlaceReplaceController implements IEditorContribution { private readonly editor: ICodeEditor; private readonly editorWorkerService: IEditorWorkerService; private decorationIds: string[] = []; - private currentRequest: CancelablePromise; - private decorationRemover: CancelablePromise; + private currentRequest?: CancelablePromise; + private decorationRemover?: CancelablePromise; constructor( editor: ICodeEditor, diff --git a/src/vs/editor/contrib/indentation/indentation.ts b/src/vs/editor/contrib/indentation/indentation.ts index 5577359b2e..ac59e69056 100644 --- a/src/vs/editor/contrib/indentation/indentation.ts +++ b/src/vs/editor/contrib/indentation/indentation.ts @@ -378,11 +378,12 @@ export class AutoIndentOnPasteCommand implements ICommand { private readonly _edits: { range: IRange; text: string; eol?: EndOfLineSequence; }[]; private readonly _initialSelection: Selection; - private _selectionId: string; + private _selectionId: string | null; constructor(edits: TextEdit[], initialSelection: Selection) { this._initialSelection = initialSelection; this._edits = []; + this._selectionId = null; for (let edit of edits) { if (edit.range && typeof edit.text === 'string') { @@ -415,7 +416,7 @@ export class AutoIndentOnPasteCommand implements ICommand { } public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { - return helper.getTrackedSelection(this._selectionId); + return helper.getTrackedSelection(this._selectionId!); } } @@ -651,7 +652,7 @@ function getIndentationEditOperations(model: ITextModel, builder: IEditOperation export class IndentationToSpacesCommand implements ICommand { - private selectionId: string; + private selectionId: string | null = null; constructor(private readonly selection: Selection, private tabSize: number) { } @@ -661,13 +662,13 @@ export class IndentationToSpacesCommand implements ICommand { } public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { - return helper.getTrackedSelection(this.selectionId); + return helper.getTrackedSelection(this.selectionId!); } } export class IndentationToTabsCommand implements ICommand { - private selectionId: string; + private selectionId: string | null = null; constructor(private readonly selection: Selection, private tabSize: number) { } @@ -677,7 +678,7 @@ export class IndentationToTabsCommand implements ICommand { } public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { - return helper.getTrackedSelection(this.selectionId); + return helper.getTrackedSelection(this.selectionId!); } } @@ -688,4 +689,4 @@ registerEditorAction(IndentUsingTabs); registerEditorAction(IndentUsingSpaces); registerEditorAction(DetectIndentation); registerEditorAction(ReindentLinesAction); -registerEditorAction(ReindentSelectedLinesAction); \ No newline at end of file +registerEditorAction(ReindentSelectedLinesAction); diff --git a/src/vs/editor/contrib/linesOperations/copyLinesCommand.ts b/src/vs/editor/contrib/linesOperations/copyLinesCommand.ts index cbce40dd7c..99aba47777 100644 --- a/src/vs/editor/contrib/linesOperations/copyLinesCommand.ts +++ b/src/vs/editor/contrib/linesOperations/copyLinesCommand.ts @@ -14,13 +14,17 @@ export class CopyLinesCommand implements editorCommon.ICommand { private readonly _isCopyingDown: boolean; private _selectionDirection: SelectionDirection; - private _selectionId: string; + private _selectionId: string | null; private _startLineNumberDelta: number; private _endLineNumberDelta: number; constructor(selection: Selection, isCopyingDown: boolean) { this._selection = selection; this._isCopyingDown = isCopyingDown; + this._selectionDirection = SelectionDirection.LTR; + this._selectionId = null; + this._startLineNumberDelta = 0; + this._endLineNumberDelta = 0; } public getEditOperations(model: ITextModel, builder: editorCommon.IEditOperationBuilder): void { @@ -58,7 +62,7 @@ export class CopyLinesCommand implements editorCommon.ICommand { } public computeCursorState(model: ITextModel, helper: editorCommon.ICursorStateComputerData): Selection { - let result = helper.getTrackedSelection(this._selectionId); + let result = helper.getTrackedSelection(this._selectionId!); if (this._startLineNumberDelta !== 0 || this._endLineNumberDelta !== 0) { let startLineNumber = result.startLineNumber; diff --git a/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts b/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts index 8c2afca2f5..6ff27f71a5 100644 --- a/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts +++ b/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts @@ -20,7 +20,7 @@ export class MoveLinesCommand implements ICommand { private readonly _isMovingDown: boolean; private readonly _autoIndent: boolean; - private _selectionId: string; + private _selectionId: string | null; private _moveEndPositionDown?: boolean; private _moveEndLineSelectionShrink: boolean; @@ -28,6 +28,7 @@ export class MoveLinesCommand implements ICommand { this._selection = selection; this._isMovingDown = isMovingDown; this._autoIndent = autoIndent; + this._selectionId = null; this._moveEndLineSelectionShrink = false; } @@ -36,9 +37,11 @@ export class MoveLinesCommand implements ICommand { let modelLineCount = model.getLineCount(); if (this._isMovingDown && this._selection.endLineNumber === modelLineCount) { + this._selectionId = builder.trackSelection(this._selection); return; } if (!this._isMovingDown && this._selection.startLineNumber === 1) { + this._selectionId = builder.trackSelection(this._selection); return; } @@ -328,7 +331,7 @@ export class MoveLinesCommand implements ICommand { } public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { - let result = helper.getTrackedSelection(this._selectionId); + let result = helper.getTrackedSelection(this._selectionId!); if (this._moveEndPositionDown) { result = result.setEndPosition(result.endLineNumber + 1, 1); diff --git a/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts b/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts index 1ef4383cc5..3dad41df20 100644 --- a/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts +++ b/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts @@ -12,12 +12,13 @@ import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/mod export class SortLinesCommand implements editorCommon.ICommand { private readonly selection: Selection; - private selectionId: string; private readonly descending: boolean; + private selectionId: string | null; constructor(selection: Selection, descending: boolean) { this.selection = selection; this.descending = descending; + this.selectionId = null; } public getEditOperations(model: ITextModel, builder: editorCommon.IEditOperationBuilder): void { @@ -30,7 +31,7 @@ export class SortLinesCommand implements editorCommon.ICommand { } public computeCursorState(model: ITextModel, helper: editorCommon.ICursorStateComputerData): Selection { - return helper.getTrackedSelection(this.selectionId); + return helper.getTrackedSelection(this.selectionId!); } public static canRun(model: ITextModel | null, selection: Selection, descending: boolean): boolean { diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index 8e246b1254..0f8eee95a7 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -299,10 +299,12 @@ class LinkDetector implements editorCommon.IEditorContribution { return this.openerService.open(uri, { openToSide }); }, err => { + const messageOrError = + err instanceof Error ? (err).message : err; // different error cases - if (err === 'invalid') { + if (messageOrError === 'invalid') { this.notificationService.warn(nls.localize('invalid.url', 'Failed to open this link because it is not well-formed: {0}', link.url!.toString())); - } else if (err === 'missing') { + } else if (messageOrError === 'missing') { this.notificationService.warn(nls.localize('missing.url', 'Failed to open this link because its target is missing.')); } else { onUnexpectedError(err); diff --git a/src/vs/editor/contrib/parameterHints/parameterHints.css b/src/vs/editor/contrib/parameterHints/parameterHints.css index 4bb2a311ce..c63bd0381b 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHints.css +++ b/src/vs/editor/contrib/parameterHints/parameterHints.css @@ -45,6 +45,10 @@ white-space: pre-wrap; } +.monaco-editor .parameter-hints-widget .docs.empty { + display: none; +} + .monaco-editor .parameter-hints-widget .docs .markdown-docs { white-space: initial; } diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts index e35702ea25..f4af5b6a2c 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts @@ -206,8 +206,6 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, dom.append(this.docs, $('p', {}, documentation)); } - dom.toggleClass(this.signature, 'has-docs', !!signature.documentation); - if (signature.documentation === undefined) { /** no op */ } else if (typeof signature.documentation === 'string') { dom.append(this.docs, $('p', {}, signature.documentation)); @@ -218,6 +216,23 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, dom.append(this.docs, renderedContents.element); } + let hasDocs = false; + if (activeParameter && typeof (activeParameter.documentation) === 'string' && activeParameter.documentation.length > 0) { + hasDocs = true; + } + if (activeParameter && typeof (activeParameter.documentation) === 'object' && activeParameter.documentation.value.length > 0) { + hasDocs = true; + } + if (typeof (signature.documentation) === 'string' && signature.documentation.length > 0) { + hasDocs = true; + } + if (typeof (signature.documentation) === 'object' && signature.documentation.value.length > 0) { + hasDocs = true; + } + + dom.toggleClass(this.signature, 'has-docs', hasDocs); + dom.toggleClass(this.docs, 'empty', !hasDocs); + let currentOverload = String(hints.activeSignature + 1); if (hints.signatures.length < 10) { diff --git a/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts b/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts index c93dfab479..0c578c3b02 100644 --- a/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts @@ -84,12 +84,12 @@ export abstract class PeekViewWidget extends ZoneWidget { private _onDidClose = new Emitter(); - protected _headElement: HTMLDivElement; - protected _primaryHeading: HTMLElement; - protected _secondaryHeading: HTMLElement; - protected _metaHeading: HTMLElement; - protected _actionbarWidget: ActionBar; - protected _bodyElement: HTMLDivElement; + protected _headElement?: HTMLDivElement; + protected _primaryHeading?: HTMLElement; + protected _secondaryHeading?: HTMLElement; + protected _metaHeading?: HTMLElement; + protected _actionbarWidget?: ActionBar; + protected _bodyElement?: HTMLDivElement; constructor(editor: ICodeEditor, options: IPeekViewOptions = {}) { super(editor, options); @@ -139,8 +139,8 @@ export abstract class PeekViewWidget extends ZoneWidget { protected _fillContainer(container: HTMLElement): void { this.setCssClass('peekview-widget'); - this._headElement = dom.$('.head'); - this._bodyElement = dom.$('.body'); + this._headElement = dom.$('.head'); + this._bodyElement = dom.$('.body'); this._fillHead(this._headElement); this._fillBody(this._bodyElement); @@ -151,7 +151,7 @@ export abstract class PeekViewWidget extends ZoneWidget { protected _fillHead(container: HTMLElement): void { const titleElement = dom.$('.peekview-title'); - dom.append(this._headElement, titleElement); + dom.append(this._headElement!, titleElement); dom.addStandardDisposableListener(titleElement, 'click', event => this._onTitleClick(event)); this._fillTitleIcon(titleElement); @@ -161,7 +161,7 @@ export abstract class PeekViewWidget extends ZoneWidget { dom.append(titleElement, this._primaryHeading, this._secondaryHeading, this._metaHeading); const actionsContainer = dom.$('.peekview-actions'); - dom.append(this._headElement, actionsContainer); + dom.append(this._headElement!, actionsContainer); const actionBarOptions = this._getActionBarOptions(); this._actionbarWidget = new ActionBar(actionsContainer, actionBarOptions); @@ -185,20 +185,24 @@ export abstract class PeekViewWidget extends ZoneWidget { } public setTitle(primaryHeading: string, secondaryHeading?: string): void { - this._primaryHeading.innerHTML = strings.escape(primaryHeading); - this._primaryHeading.setAttribute('aria-label', primaryHeading); - if (secondaryHeading) { - this._secondaryHeading.innerHTML = strings.escape(secondaryHeading); - } else { - dom.clearNode(this._secondaryHeading); + if (this._primaryHeading && this._secondaryHeading) { + this._primaryHeading.innerHTML = strings.escape(primaryHeading); + this._primaryHeading.setAttribute('aria-label', primaryHeading); + if (secondaryHeading) { + this._secondaryHeading.innerHTML = strings.escape(secondaryHeading); + } else { + dom.clearNode(this._secondaryHeading); + } } } public setMetaTitle(value: string): void { - if (value) { - this._metaHeading.innerHTML = strings.escape(value); - } else { - dom.clearNode(this._metaHeading); + if (this._metaHeading) { + if (value) { + this._metaHeading.innerHTML = strings.escape(value); + } else { + dom.clearNode(this._metaHeading); + } } } @@ -220,11 +224,15 @@ export abstract class PeekViewWidget extends ZoneWidget { } protected _doLayoutHead(heightInPixel: number, widthInPixel: number): void { - this._headElement.style.height = `${heightInPixel}px`; - this._headElement.style.lineHeight = this._headElement.style.height; + if (this._headElement) { + this._headElement.style.height = `${heightInPixel}px`; + this._headElement.style.lineHeight = this._headElement.style.height; + } } protected _doLayoutBody(heightInPixel: number, widthInPixel: number): void { - this._bodyElement.style.height = `${heightInPixel}px`; + if (this._bodyElement) { + this._bodyElement.style.height = `${heightInPixel}px`; + } } } diff --git a/src/vs/editor/contrib/referenceSearch/referencesController.ts b/src/vs/editor/contrib/referenceSearch/referencesController.ts index 5e230bac05..60164ff751 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesController.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesController.ts @@ -34,8 +34,8 @@ export abstract class ReferencesController implements editorCommon.IEditorContri private readonly _disposables = new DisposableStore(); private readonly _editor: ICodeEditor; - private _widget: ReferenceWidget | null; - private _model: ReferencesModel | null; + private _widget?: ReferenceWidget; + private _model?: ReferencesModel; private _requestIdPool = 0; private _ignoreModelChangeEvent = false; @@ -68,11 +68,11 @@ export abstract class ReferencesController implements editorCommon.IEditorContri dispose(this._disposables); if (this._widget) { dispose(this._widget); - this._widget = null; + this._widget = undefined; } if (this._model) { dispose(this._model); - this._model = null; + this._model = undefined; } } @@ -107,7 +107,7 @@ export abstract class ReferencesController implements editorCommon.IEditorContri modelPromise.cancel(); if (this._widget) { this._storageService.store(storageKey, JSON.stringify(this._widget.layoutData), StorageScope.GLOBAL); - this._widget = null; + this._widget = undefined; } this.closeWidget(); })); @@ -202,13 +202,13 @@ export abstract class ReferencesController implements editorCommon.IEditorContri public closeWidget(): void { if (this._widget) { dispose(this._widget); - this._widget = null; + this._widget = undefined; } this._referenceSearchVisible.reset(); this._disposables.clear(); if (this._model) { dispose(this._model); - this._model = null; + this._model = undefined; } this._editor.focus(); this._requestIdPool += 1; // Cancel pending requests diff --git a/src/vs/editor/contrib/referenceSearch/referencesModel.ts b/src/vs/editor/contrib/referenceSearch/referencesModel.ts index f6a651ecc4..a7ebf11dfa 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesModel.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesModel.ts @@ -89,7 +89,7 @@ export class FileReferences implements IDisposable { private _children: OneReference[]; private _preview?: FilePreview; - private _resolved: boolean; + private _resolved?: boolean; private _loadFailure: any; constructor(private readonly _parent: ReferencesModel, private readonly _uri: URI) { diff --git a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts index 5f15ead33f..1c6fc3fb46 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts @@ -158,8 +158,8 @@ class DecorationsManager implements IDisposable { } export class LayoutData { - ratio: number; - heightInLines: number; + ratio: number = 0.7; + heightInLines: number = 18; static fromJSON(raw: string): LayoutData { let ratio: number | undefined; @@ -191,22 +191,21 @@ export const ctxReferenceWidgetSearchTreeFocused = new RawContextKey('r */ export class ReferenceWidget extends PeekViewWidget { - private _model: ReferencesModel | undefined; - private _decorationsManager: DecorationsManager; + private _model?: ReferencesModel; + private _decorationsManager?: DecorationsManager; private readonly _disposeOnNewModel = new DisposableStore(); private readonly _callOnDispose = new DisposableStore(); private _onDidSelectReference = new Emitter(); - private _tree: WorkbenchAsyncDataTree; - private _treeContainer: HTMLElement; - // private _sash: VSash; - private _splitView: SplitView; - private _preview: ICodeEditor; - private _previewModelReference: IReference; - private _previewNotAvailableMessage: TextModel; - private _previewContainer: HTMLElement; - private _messageContainer: HTMLElement; + private _tree!: WorkbenchAsyncDataTree; + private _treeContainer!: HTMLElement; + private _splitView!: SplitView; + private _preview!: ICodeEditor; + private _previewModelReference!: IReference; + private _previewNotAvailableMessage!: TextModel; + private _previewContainer!: HTMLElement; + private _messageContainer!: HTMLElement; private _dim: dom.Dimension = { height: 0, width: 0 }; constructor( @@ -470,7 +469,7 @@ export class ReferenceWidget extends PeekViewWidget { })); // make sure things are rendered - dom.addClass(this.container, 'results-loaded'); + dom.addClass(this.container!, 'results-loaded'); dom.show(this._treeContainer); dom.show(this._previewContainer); this._splitView.layout(this._dim.width); diff --git a/src/vs/editor/contrib/rename/renameInputField.ts b/src/vs/editor/contrib/rename/renameInputField.ts index 3b6042c5f9..40e057e3e9 100644 --- a/src/vs/editor/contrib/rename/renameInputField.ts +++ b/src/vs/editor/contrib/rename/renameInputField.ts @@ -19,10 +19,10 @@ export const CONTEXT_RENAME_INPUT_VISIBLE = new RawContextKey('renameIn export class RenameInputField implements IContentWidget, IDisposable { private _editor: ICodeEditor; - private _position: Position; - private _domNode: HTMLElement; - private _inputField: HTMLInputElement; - private _visible: boolean; + private _position?: Position; + private _domNode?: HTMLElement; + private _inputField?: HTMLInputElement; + private _visible?: boolean; private readonly _visibleContextKey: IContextKey; private readonly _disposables = new DisposableStore(); @@ -95,7 +95,7 @@ export class RenameInputField implements IContentWidget, IDisposable { this._inputField.style.borderStyle = border ? 'solid' : 'none'; this._inputField.style.borderColor = border ? border.toString() : 'none'; - this._domNode.style.boxShadow = widgetShadowColor ? ` 0 2px 8px ${widgetShadowColor}` : null; + this._domNode!.style.boxShadow = widgetShadowColor ? ` 0 2px 8px ${widgetShadowColor}` : null; } private updateFont(): void { @@ -111,7 +111,7 @@ export class RenameInputField implements IContentWidget, IDisposable { public getPosition(): IContentWidgetPosition | null { return this._visible - ? { position: this._position, preference: [ContentWidgetPositionPreference.BELOW, ContentWidgetPositionPreference.ABOVE] } + ? { position: this._position!, preference: [ContentWidgetPositionPreference.BELOW, ContentWidgetPositionPreference.ABOVE] } : null; } @@ -133,10 +133,10 @@ export class RenameInputField implements IContentWidget, IDisposable { public getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number): Promise { this._position = new Position(where.startLineNumber, where.startColumn); - this._inputField.value = value; - this._inputField.setAttribute('selectionStart', selectionStart.toString()); - this._inputField.setAttribute('selectionEnd', selectionEnd.toString()); - this._inputField.size = Math.max((where.endColumn - where.startColumn) * 1.1, 20); + this._inputField!.value = value; + this._inputField!.setAttribute('selectionStart', selectionStart.toString()); + this._inputField!.setAttribute('selectionEnd', selectionEnd.toString()); + this._inputField!.size = Math.max((where.endColumn - where.startColumn) * 1.1, 20); const disposeOnDone = new DisposableStore(); const always = () => { @@ -154,7 +154,7 @@ export class RenameInputField implements IContentWidget, IDisposable { }; this._currentAcceptInput = () => { - if (this._inputField.value.trim().length === 0 || this._inputField.value === value) { + if (this._inputField!.value.trim().length === 0 || this._inputField!.value === value) { // empty or whitespace only or not changed this.cancelInput(true); return; @@ -162,7 +162,7 @@ export class RenameInputField implements IContentWidget, IDisposable { this._currentAcceptInput = null; this._currentCancelInput = null; - resolve(this._inputField.value); + resolve(this._inputField!.value); }; let onCursorChanged = () => { @@ -187,16 +187,16 @@ export class RenameInputField implements IContentWidget, IDisposable { } private _show(): void { - this._editor.revealLineInCenterIfOutsideViewport(this._position.lineNumber, ScrollType.Smooth); + this._editor.revealLineInCenterIfOutsideViewport(this._position!.lineNumber, ScrollType.Smooth); this._visible = true; this._visibleContextKey.set(true); this._editor.layoutContentWidget(this); setTimeout(() => { - this._inputField.focus(); - this._inputField.setSelectionRange( - parseInt(this._inputField.getAttribute('selectionStart')!), - parseInt(this._inputField.getAttribute('selectionEnd')!)); + this._inputField!.focus(); + this._inputField!.setSelectionRange( + parseInt(this._inputField!.getAttribute('selectionStart')!), + parseInt(this._inputField!.getAttribute('selectionEnd')!)); }, 100); } diff --git a/src/vs/editor/contrib/snippet/snippetController2.ts b/src/vs/editor/contrib/snippet/snippetController2.ts index 7fd0f4dd80..51b9247746 100644 --- a/src/vs/editor/contrib/snippet/snippetController2.ts +++ b/src/vs/editor/contrib/snippet/snippetController2.ts @@ -54,7 +54,7 @@ export class SnippetController2 implements IEditorContribution { private _session?: SnippetSession; private _snippetListener = new DisposableStore(); - private _modelVersionId: number; + private _modelVersionId: number = -1; private _currentChoice?: Choice; constructor( diff --git a/src/vs/editor/contrib/snippet/snippetParser.ts b/src/vs/editor/contrib/snippet/snippetParser.ts index adaca75ace..3ebc841708 100644 --- a/src/vs/editor/contrib/snippet/snippetParser.ts +++ b/src/vs/editor/contrib/snippet/snippetParser.ts @@ -56,12 +56,8 @@ export class Scanner { || (ch >= CharCode.A && ch <= CharCode.Z); } - value: string; - pos: number; - - constructor() { - this.text(''); - } + value: string = ''; + pos: number = 0; text(value: string) { this.value = value; @@ -135,7 +131,7 @@ export abstract class Marker { readonly _markerBrand: any; - public parent: Marker; + public parent!: Marker; protected _children: Marker[] = []; appendChild(child: Marker): this { @@ -219,7 +215,7 @@ export class Text extends Marker { } export abstract class TransformableMarker extends Marker { - public transform: Transform; + public transform?: Transform; } export class Placeholder extends TransformableMarker { @@ -314,7 +310,7 @@ export class Choice extends Marker { export class Transform extends Marker { - regexp: RegExp; + regexp: RegExp = new RegExp(''); resolve(value: string): string { const _this = this; @@ -590,8 +586,8 @@ export class SnippetParser { return value.replace(/\$|}|\\/g, '\\$&'); } - private _scanner = new Scanner(); - private _token: Token; + private _scanner: Scanner = new Scanner(); + private _token: Token = { type: TokenType.EOF, pos: 0, len: 0 }; text(value: string): string { return this.parse(value).toString(); @@ -671,16 +667,24 @@ export class SnippetParser { if (this._token.type === TokenType.EOF) { return false; } - let start = this._token; - while (this._token.type !== type) { + let res = ''; + let pos = this._token.pos; + let prevToken = { type: TokenType.EOF, pos: 0, len: 0 }; + + while (this._token.type !== type || prevToken.type === TokenType.Backslash) { + if (this._token.type === type) { + res += this._scanner.value.substring(pos, prevToken.pos); + pos = this._token.pos; + } + prevToken = this._token; this._token = this._scanner.next(); if (this._token.type === TokenType.EOF) { return false; } } - let value = this._scanner.value.substring(start.pos, this._token.pos); + res += this._scanner.value.substring(pos, this._token.pos); this._token = this._scanner.next(); - return value; + return res; } private _parse(marker: Marker): boolean { diff --git a/src/vs/editor/contrib/snippet/snippetSession.ts b/src/vs/editor/contrib/snippet/snippetSession.ts index 0292ba7422..a7c7419c41 100644 --- a/src/vs/editor/contrib/snippet/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/snippetSession.ts @@ -41,7 +41,7 @@ export class OneSnippet { private readonly _snippet: TextmateSnippet; private readonly _offset: number; - private _placeholderDecorations: Map; + private _placeholderDecorations?: Map; private _placeholderGroups: Placeholder[][]; _placeholderGroupsIdx: number; _nestingLevel: number = 1; @@ -92,7 +92,7 @@ export class OneSnippet { ); const options = placeholder.isFinalTabstop ? OneSnippet._decor.inactiveFinal : OneSnippet._decor.inactive; const handle = accessor.addDecoration(range, options); - this._placeholderDecorations.set(placeholder, handle); + this._placeholderDecorations!.set(placeholder, handle); } }); } @@ -111,7 +111,7 @@ export class OneSnippet { for (const placeholder of this._placeholderGroups[this._placeholderGroupsIdx]) { // Check if the placeholder has a transformation if (placeholder.transform) { - const id = this._placeholderDecorations.get(placeholder)!; + const id = this._placeholderDecorations!.get(placeholder)!; const range = this._editor.getModel().getDecorationRange(id)!; const currentValue = this._editor.getModel().getValueInRange(range); @@ -148,7 +148,7 @@ export class OneSnippet { // Special case #2: placeholders enclosing active placeholders const selections: Selection[] = []; for (const placeholder of this._placeholderGroups[this._placeholderGroupsIdx]) { - const id = this._placeholderDecorations.get(placeholder)!; + const id = this._placeholderDecorations!.get(placeholder)!; const range = this._editor.getModel().getDecorationRange(id)!; selections.push(new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn)); @@ -161,7 +161,7 @@ export class OneSnippet { activePlaceholders.add(placeholder); for (const enclosingPlaceholder of this._snippet.enclosingPlaceholders(placeholder)) { - const id = this._placeholderDecorations.get(enclosingPlaceholder)!; + const id = this._placeholderDecorations!.get(enclosingPlaceholder)!; accessor.changeDecorationOptions(id, enclosingPlaceholder.isFinalTabstop ? OneSnippet._decor.activeFinal : OneSnippet._decor.active); activePlaceholders.add(enclosingPlaceholder); } @@ -169,7 +169,7 @@ export class OneSnippet { // change stickness to never grow when typing at its edges // so that in-active tabstops never grow - this._placeholderDecorations.forEach((id, placeholder) => { + this._placeholderDecorations!.forEach((id, placeholder) => { if (!activePlaceholders.has(placeholder)) { accessor.changeDecorationOptions(id, placeholder.isFinalTabstop ? OneSnippet._decor.inactiveFinal : OneSnippet._decor.inactive); } @@ -188,7 +188,7 @@ export class OneSnippet { let marker: Marker | undefined = placeholder; while (marker) { if (marker instanceof Placeholder) { - const id = this._placeholderDecorations.get(marker)!; + const id = this._placeholderDecorations!.get(marker)!; const range = this._editor.getModel().getDecorationRange(id)!; if (range.isEmpty() && marker.toString().length > 0) { return true; @@ -227,7 +227,7 @@ export class OneSnippet { result.set(placeholder.index, ranges); } - const id = this._placeholderDecorations.get(placeholder)!; + const id = this._placeholderDecorations!.get(placeholder)!; const range = this._editor.getModel().getDecorationRange(id); if (!range) { // one of the placeholder lost its decoration and @@ -278,9 +278,9 @@ export class OneSnippet { // Remove the placeholder at which position are inserting // the snippet and also remove its decoration. - const id = this._placeholderDecorations.get(placeholder)!; + const id = this._placeholderDecorations!.get(placeholder)!; accessor.removeDecoration(id); - this._placeholderDecorations.delete(placeholder); + this._placeholderDecorations!.delete(placeholder); // For each *new* placeholder we create decoration to monitor // how and if it grows/shrinks. @@ -292,7 +292,7 @@ export class OneSnippet { model.getPositionAt(nested._offset + placeholderOffset + placeholderLen) ); const handle = accessor.addDecoration(range, OneSnippet._decor.inactive); - this._placeholderDecorations.set(placeholder, handle); + this._placeholderDecorations!.set(placeholder, handle); } } @@ -304,7 +304,7 @@ export class OneSnippet { public getEnclosingRange(): Range | undefined { let result: Range | undefined; const model = this._editor.getModel(); - this._placeholderDecorations.forEach((decorationId) => { + this._placeholderDecorations!.forEach((decorationId) => { const placeholderRange = withNullAsUndefined(model.getDecorationRange(decorationId)); if (!result) { result = placeholderRange; diff --git a/src/vs/editor/contrib/snippet/test/snippetParser.test.ts b/src/vs/editor/contrib/snippet/test/snippetParser.test.ts index e2b81256a2..71be413886 100644 --- a/src/vs/editor/contrib/snippet/test/snippetParser.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetParser.test.ts @@ -418,7 +418,7 @@ suite('SnippetParser', () => { assert.ok(children[3] instanceof Placeholder); assert.equal(children[3].children.length, 0); assert.notEqual((children[3]).transform, undefined); - let transform = (children[3]).transform; + let transform = (children[3]).transform!; assert.equal(transform.regexp, '/\\s:=(.*)/'); assert.equal(transform.children.length, 2); assert.ok(transform.children[0] instanceof FormatString); @@ -754,4 +754,17 @@ suite('SnippetParser', () => { let snippet = new SnippetParser().parse('namespace ${TM_DIRECTORY/[\\/]/\\\\/g};'); assertMarker(snippet, Text, Variable, Text); }); + + test('Snippet cannot escape closing bracket inside conditional insertion variable replacement #78883', function () { + + let snippet = new SnippetParser().parse('${TM_DIRECTORY/(.+)/${1:+import { hello \\} from world}/}'); + let variable = snippet.children[0]; + assert.equal(snippet.children.length, 1); + assert.ok(variable instanceof Variable); + assert.ok(variable.transform); + assert.equal(variable.transform!.children.length, 1); + assert.ok(variable.transform!.children[0] instanceof FormatString); + assert.equal((variable.transform!.children[0]).ifValue, 'import { hello } from world'); + assert.equal((variable.transform!.children[0]).elseValue, undefined); + }); }); diff --git a/src/vs/editor/contrib/suggest/completionModel.ts b/src/vs/editor/contrib/suggest/completionModel.ts index bc5c38cf20..25721be674 100644 --- a/src/vs/editor/contrib/suggest/completionModel.ts +++ b/src/vs/editor/contrib/suggest/completionModel.ts @@ -29,8 +29,10 @@ export interface ICompletionStats { } export class LineContext { - leadingLineContent: string; - characterCountDelta: number; + constructor( + readonly leadingLineContent: string, + readonly characterCountDelta: number, + ) { } } const enum Refilter { @@ -49,9 +51,9 @@ export class CompletionModel { private _lineContext: LineContext; private _refilterKind: Refilter; - private _filteredItems: StrictCompletionItem[]; - private _isIncomplete: Set; - private _stats: ICompletionStats; + private _filteredItems?: StrictCompletionItem[]; + private _isIncomplete?: Set; + private _stats?: ICompletionStats; constructor( items: CompletionItem[], @@ -89,12 +91,12 @@ export class CompletionModel { get items(): CompletionItem[] { this._ensureCachedState(); - return this._filteredItems; + return this._filteredItems!; } get incomplete(): Set { this._ensureCachedState(); - return this._isIncomplete; + return this._isIncomplete!; } adopt(except: Set): CompletionItem[] { @@ -117,7 +119,7 @@ export class CompletionModel { get stats(): ICompletionStats { this._ensureCachedState(); - return this._stats; + return this._stats!; } private _ensureCachedState(): void { @@ -136,7 +138,7 @@ export class CompletionModel { let wordLow = ''; // incrementally filter less - const source = this._refilterKind === Refilter.All ? this._items : this._filteredItems; + const source = this._refilterKind === Refilter.All ? this._items : this._filteredItems!; const target: StrictCompletionItem[] = []; // picks a score function based on the number of diff --git a/src/vs/editor/contrib/suggest/media/suggest.css b/src/vs/editor/contrib/suggest/media/suggest.css index 9022269946..9dcfd89867 100644 --- a/src/vs/editor/contrib/suggest/media/suggest.css +++ b/src/vs/editor/contrib/suggest/media/suggest.css @@ -110,7 +110,9 @@ .monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .header > .close { background-image: url('./close-light.svg'); - float: right; + position: absolute; + top: 0px; + right: 0px; margin-right: 5px; } @@ -251,7 +253,7 @@ text-overflow: ellipsis; opacity: 0.7; word-break: break-all; - margin: 0; + margin: 0px 24px 0 0; padding: 4px 0 12px 5px; } diff --git a/src/vs/editor/contrib/suggest/suggest.ts b/src/vs/editor/contrib/suggest/suggest.ts index f02c7365c8..9a4052ca07 100644 --- a/src/vs/editor/contrib/suggest/suggest.ts +++ b/src/vs/editor/contrib/suggest/suggest.ts @@ -27,7 +27,7 @@ export const Context = { export class CompletionItem { - _brand: 'ISuggestionItem'; + _brand!: 'ISuggestionItem'; readonly resolve: (token: CancellationToken) => Promise; diff --git a/src/vs/editor/contrib/suggest/suggestAlternatives.ts b/src/vs/editor/contrib/suggest/suggestAlternatives.ts index 1df6a49b85..5cdc3e9e8b 100644 --- a/src/vs/editor/contrib/suggest/suggestAlternatives.ts +++ b/src/vs/editor/contrib/suggest/suggestAlternatives.ts @@ -15,7 +15,7 @@ export class SuggestAlternatives { private readonly _ckOtherSuggestions: IContextKey; - private _index: number; + private _index: number = 0; private _model: CompletionModel | undefined; private _acceptNext: ((selected: ISelectedSuggestion) => any) | undefined; private _listener: IDisposable | undefined; diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index 84a6297d40..4c5b089455 100644 --- a/src/vs/editor/contrib/suggest/suggestController.ts +++ b/src/vs/editor/contrib/suggest/suggestController.ts @@ -370,6 +370,10 @@ export class SuggestController implements IEditorContribution { this._widget.getValue().toggleDetails(); } + toggleExplainMode(): void { + this._widget.getValue().toggleExplainMode(); + } + toggleSuggestionFocus(): void { this._widget.getValue().toggleDetailsFocus(); } @@ -521,6 +525,16 @@ registerEditorCommand(new SuggestCommand({ } })); +registerEditorCommand(new SuggestCommand({ + id: 'toggleExplainMode', + precondition: SuggestContext.Visible, + handler: x => x.toggleExplainMode(), + kbOpts: { + weight: KeybindingWeight.EditorContrib, + primary: KeyMod.CtrlCmd | KeyCode.US_SLASH, + } +})); + registerEditorCommand(new SuggestCommand({ id: 'toggleSuggestionFocus', precondition: SuggestContext.Visible, diff --git a/src/vs/editor/contrib/suggest/suggestMemory.ts b/src/vs/editor/contrib/suggest/suggestMemory.ts index 8da7ffb489..9b8c863a70 100644 --- a/src/vs/editor/contrib/suggest/suggestMemory.ts +++ b/src/vs/editor/contrib/suggest/suggestMemory.ts @@ -22,10 +22,10 @@ export abstract class Memory { if (items.length === 0) { return 0; } - let topScore = items[0].score; + let topScore = items[0].score[0]; for (let i = 1; i < items.length; i++) { const { score, completion: suggestion } = items[i]; - if (score !== topScore) { + if (score[0] !== topScore) { // stop when leaving the group of top matches break; } @@ -81,33 +81,42 @@ export class LRUMemory extends Memory { } select(model: ITextModel, pos: IPosition, items: CompletionItem[]): number { - // in order of completions, select the first - // that has been used in the past - let { word } = model.getWordUntilPosition(pos); - if (word.length !== 0) { - return super.select(model, pos, items); + + if (items.length === 0) { + return 0; } - let lineSuffix = model.getLineContent(pos.lineNumber).substr(pos.column - 10, pos.column - 1); + const lineSuffix = model.getLineContent(pos.lineNumber).substr(pos.column - 10, pos.column - 1); if (/\s$/.test(lineSuffix)) { return super.select(model, pos, items); } - let res = -1; + let topScore = items[0].score[0]; + let indexPreselect = -1; + let indexRecency = -1; let seq = -1; for (let i = 0; i < items.length; i++) { - const { completion: suggestion } = items[i]; - const key = `${model.getLanguageIdentifier().language}/${suggestion.label}`; - const item = this._cache.get(key); - if (item && item.touch > seq && item.type === suggestion.kind && item.insertText === suggestion.insertText) { + if (items[i].score[0] !== topScore) { + // consider only top items + break; + } + const key = `${model.getLanguageIdentifier().language}/${items[i].completion.label}`; + const item = this._cache.peek(key); + if (item && item.touch > seq && item.type === items[i].completion.kind && item.insertText === items[i].completion.insertText) { seq = item.touch; - res = i; + indexRecency = i; + } + if (items[i].completion.preselect && indexPreselect === -1) { + // stop when seeing an auto-select-item + return indexPreselect = i; } } - if (res === -1) { - return super.select(model, pos, items); + if (indexRecency !== -1) { + return indexRecency; + } else if (indexPreselect !== -1) { + return indexPreselect; } else { - return res; + return 0; } } @@ -204,9 +213,9 @@ export class SuggestMemoryService extends Disposable implements ISuggestMemorySe private readonly _storagePrefix = 'suggest/memories'; private readonly _persistSoon: RunOnceScheduler; - private _mode: MemMode; - private _shareMem: boolean; - private _strategy: Memory; + private _mode!: MemMode; + private _shareMem!: boolean; + private _strategy!: Memory; constructor( @IStorageService private readonly _storageService: IStorageService, diff --git a/src/vs/editor/contrib/suggest/suggestModel.ts b/src/vs/editor/contrib/suggest/suggestModel.ts index a951fefb5a..a483b270a1 100644 --- a/src/vs/editor/contrib/suggest/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/suggestModel.ts @@ -92,8 +92,8 @@ export const enum State { export class SuggestModel implements IDisposable { private readonly _toDispose = new DisposableStore(); - private _quickSuggestDelay: number; - private _triggerCharacterListener: IDisposable; + private _quickSuggestDelay: number = 10; + private _triggerCharacterListener?: IDisposable; private readonly _triggerQuickSuggest = new TimeoutTimer(); private _state: State = State.Idle; @@ -161,7 +161,8 @@ export class SuggestModel implements IDisposable { } dispose(): void { - dispose([this._onDidCancel, this._onDidSuggest, this._onDidTrigger, this._triggerCharacterListener, this._triggerQuickSuggest]); + dispose(this._triggerCharacterListener); + dispose([this._onDidCancel, this._onDidSuggest, this._onDidTrigger, this._triggerQuickSuggest]); this._toDispose.dispose(); this._completionDisposables.dispose(); this.cancel(); diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 19f1ac6631..835c9c6e46 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -38,8 +38,6 @@ import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { FileKind } from 'vs/platform/files/common/files'; import { MarkdownString } from 'vs/base/common/htmlContent'; -import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; const expandSuggestionDocsByDefault = false; @@ -117,7 +115,7 @@ class Renderer implements IListRenderer const text = append(container, $('.contents')); const main = append(text, $('.main')); - data.iconLabel = new IconLabel(main, { supportHighlights: true }); + data.iconLabel = new IconLabel(main, { supportHighlights: true, supportOcticons: true }); data.disposables.add(data.iconLabel); data.typeLabel = append(main, $('span.type-label')); @@ -231,17 +229,6 @@ const enum State { } -let _explainMode = false; -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'suggest.toggleExplainMode', - handler() { - _explainMode = !_explainMode; - }, - when: SuggestContext.Visible, - weight: KeybindingWeight.EditorContrib, - primary: KeyMod.CtrlCmd | KeyCode.US_SLASH, -}); - class SuggestionDetails { private el: HTMLElement; @@ -253,7 +240,7 @@ class SuggestionDetails { private docs: HTMLElement; private ariaLabel: string | null; private readonly disposables: DisposableStore; - private renderDisposeable: IDisposable; + private renderDisposeable?: IDisposable; private borderWidth: number = 1; constructor( @@ -300,13 +287,13 @@ class SuggestionDetails { this.docs.textContent = ''; } - renderItem(item: CompletionItem): void { + renderItem(item: CompletionItem, explainMode: boolean): void { this.renderDisposeable = dispose(this.renderDisposeable); let { documentation, detail } = item.completion; // --- documentation - if (_explainMode) { + if (explainMode) { let md = ''; md += `score: ${item.score[0]}${item.word ? `, compared '${item.completion.filterText && (item.completion.filterText + ' (filterText)') || item.completion.label}' with '${item.word}'` : ' (no prefix)'}\n`; md += `distance: ${item.distance}, see localityBonus-setting\n`; @@ -315,7 +302,7 @@ class SuggestionDetails { detail = `Provider: ${item.provider._debugDisplayName}`; } - if (!_explainMode && !canExpandCompletionItem(item)) { + if (!explainMode && !canExpandCompletionItem(item)) { this.type.textContent = ''; this.docs.textContent = ''; addClass(this.el, 'no-docs'); @@ -435,25 +422,24 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate | null; + private currentSuggestionDetails: CancelablePromise | null = null; private focusedItem: CompletionItem | null; - private ignoreFocusEvents = false; - private completionModel: CompletionModel | null; + private ignoreFocusEvents: boolean = false; + private completionModel: CompletionModel | null = null; private element: HTMLElement; private messageElement: HTMLElement; private listElement: HTMLElement; private details: SuggestionDetails; private list: List; - private listHeight: number; + private listHeight?: number; private readonly suggestWidgetVisible: IContextKey; private readonly suggestWidgetMultipleSuggestions: IContextKey; - private readonly editorBlurTimeout = new TimeoutTimer(); private readonly showTimeout = new TimeoutTimer(); private readonly toDispose = new DisposableStore(); @@ -470,13 +456,14 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate ignore recent items + + mem.memorize(buffer, { lineNumber: 1, column: 9 }, item2); + assert.equal(mem.select(buffer, { lineNumber: 1, column: 9 }, items), 1); // foo. + + buffer.setValue(' foo.g'); + assert.equal(mem.select(buffer, { lineNumber: 1, column: 10 }, items), 1); // foo.g, 'gamma' and 'game' have the same score + + item1.score = [10, 0, 0]; + assert.equal(mem.select(buffer, { lineNumber: 1, column: 10 }, items), 0); // foo.g, 'gamma' has higher score + + }); + test('intellisense is not showing top options first #43429', function () { // ensure we don't memorize for whitespace prefixes diff --git a/src/vs/editor/contrib/suggest/wordContextKey.ts b/src/vs/editor/contrib/suggest/wordContextKey.ts index 6c196825a1..6a520198a3 100644 --- a/src/vs/editor/contrib/suggest/wordContextKey.ts +++ b/src/vs/editor/contrib/suggest/wordContextKey.ts @@ -13,7 +13,7 @@ export class WordContextKey extends Disposable { private readonly _ckAtEnd: IContextKey; - private _enabled: boolean; + private _enabled: boolean = false; private _selectionListener?: IDisposable; constructor( diff --git a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts index 1fba1943d0..db2ec8fb9e 100644 --- a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts @@ -469,6 +469,7 @@ class WordHighlighterContribution extends Disposable implements editorCommon.IEd constructor(editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService) { super(); + this.wordHighligher = null; const createWordHighlighterIfPossible = () => { if (editor.hasModel()) { this.wordHighligher = new WordHighlighter(editor, contextKeyService); diff --git a/src/vs/editor/contrib/zoneWidget/zoneWidget.ts b/src/vs/editor/contrib/zoneWidget/zoneWidget.ts index 75f4a5a131..91e54ed2de 100644 --- a/src/vs/editor/contrib/zoneWidget/zoneWidget.ts +++ b/src/vs/editor/contrib/zoneWidget/zoneWidget.ts @@ -51,7 +51,7 @@ const WIDGET_ID = 'vs.editor.contrib.zoneWidget'; export class ViewZoneDelegate implements IViewZone { public domNode: HTMLElement; - public id: number; + public id: number = 0; // A valid zone id should be greater than 0 public afterLineNumber: number; public afterColumn: number; public heightInLines: number; @@ -109,8 +109,8 @@ class Arrow { private readonly _ruleName = Arrow._IdGenerator.nextId(); private _decorations: string[] = []; - private _color: string; - private _height: number; + private _color: string | null = null; + private _height: number = -1; constructor( private readonly _editor: ICodeEditor @@ -159,15 +159,15 @@ class Arrow { export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { - private _arrow: Arrow; - private _overlayWidget: OverlayWidgetDelegate | null; - private _resizeSash: Sash; + private _arrow: Arrow | null = null; + private _overlayWidget: OverlayWidgetDelegate | null = null; + private _resizeSash: Sash | null = null; private _positionMarkerId: string[] = []; - protected _viewZone: ViewZoneDelegate | null; + protected _viewZone: ViewZoneDelegate | null = null; protected readonly _disposables = new DisposableStore(); - public container: HTMLElement; + public container: HTMLElement | null = null; public domNode: HTMLElement; public editor: ICodeEditor; public options: IOptions; @@ -273,12 +273,16 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { private _onViewZoneHeight(height: number): void { this.domNode.style.height = `${height}px`; - let containerHeight = height - this._decoratingElementsHeight(); - this.container.style.height = `${containerHeight}px`; - const layoutInfo = this.editor.getLayoutInfo(); - this._doLayout(containerHeight, this._getWidth(layoutInfo)); + if (this.container) { + let containerHeight = height - this._decoratingElementsHeight(); + this.container.style.height = `${containerHeight}px`; + const layoutInfo = this.editor.getLayoutInfo(); + this._doLayout(containerHeight, this._getWidth(layoutInfo)); + } - this._resizeSash.layout(); + if (this._resizeSash) { + this._resizeSash.layout(); + } } public get position(): Position | undefined { @@ -373,7 +377,7 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { let frameThickness = 0; // Render the arrow one 1/3 of an editor line height - if (this.options.showArrow) { + if (this._arrow && this.options.showArrow) { arrowHeight = Math.round(lineHeight / 3); this._arrow.height = arrowHeight; this._arrow.show(position); @@ -407,17 +411,19 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { this.editor.addOverlayWidget(this._overlayWidget); }); - if (this.options.showFrame) { + if (this.container && this.options.showFrame) { const width = this.options.frameWidth ? this.options.frameWidth : frameThickness; this.container.style.borderTopWidth = width + 'px'; this.container.style.borderBottomWidth = width + 'px'; } let containerHeight = heightInLines * lineHeight - this._decoratingElementsHeight(); - this.container.style.top = arrowHeight + 'px'; - this.container.style.height = containerHeight + 'px'; - this.container.style.overflow = 'hidden'; + if (this.container) { + this.container.style.top = arrowHeight + 'px'; + this.container.style.height = containerHeight + 'px'; + this.container.style.overflow = 'hidden'; + } this._doLayout(containerHeight, width); @@ -438,6 +444,10 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { } protected setCssClass(className: string, classToReplace?: string): void { + if (!this.container) { + return; + } + if (classToReplace) { this.container.classList.remove(classToReplace); } diff --git a/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts b/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts index 2ff84f1def..7ae8de2d28 100644 --- a/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts +++ b/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts @@ -21,6 +21,7 @@ export class IPadShowKeyboard extends Disposable implements IEditorContribution constructor(editor: ICodeEditor) { super(); this.editor = editor; + this.widget = null; if (browser.isIPad) { this._register(editor.onDidChangeConfiguration(() => this.update())); this.update(); diff --git a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts b/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts index e66ae5505c..a0c80d9c9e 100644 --- a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts +++ b/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts @@ -31,9 +31,9 @@ export class QuickOpenController implements editorCommon.IEditorContribution, ID } private readonly editor: ICodeEditor; - private widget: QuickOpenEditorWidget | null; - private rangeHighlightDecorationId: string | null; - private lastKnownEditorSelection: Selection | null; + private widget: QuickOpenEditorWidget | null = null; + private rangeHighlightDecorationId: string | null = null; + private lastKnownEditorSelection: Selection | null = null; constructor(editor: ICodeEditor, @IThemeService private readonly themeService: IThemeService) { this.editor = editor; diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index e488952472..db745880d3 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -98,17 +98,20 @@ function withTypedEditor(widget: editorCommon.IEditor, codeEditorCallback: (e export class SimpleEditorModelResolverService implements ITextModelService { public _serviceBrand: any; - private editor: editorCommon.IEditor; + private editor?: editorCommon.IEditor; public setEditor(editor: editorCommon.IEditor): void { this.editor = editor; } public createModelReference(resource: URI): Promise> { - let model: ITextModel | null = withTypedEditor(this.editor, - (editor) => this.findModel(editor, resource), - (diffEditor) => this.findModel(diffEditor.getOriginalEditor(), resource) || this.findModel(diffEditor.getModifiedEditor(), resource) - ); + let model: ITextModel | null = null; + if (this.editor) { + model = withTypedEditor(this.editor, + (editor) => this.findModel(editor, resource), + (diffEditor) => this.findModel(diffEditor.getOriginalEditor(), resource) || this.findModel(diffEditor.getModifiedEditor(), resource) + ); + } if (!model) { return Promise.reject(new Error(`Model not found`)); @@ -477,12 +480,12 @@ export class SimpleResourceConfigurationService implements ITextResourceConfigur _serviceBrand: any; - public readonly onDidChangeConfiguration: Event; - private readonly _onDidChangeConfigurationEmitter = new Emitter(); + private readonly _onDidChangeConfiguration = new Emitter(); + public readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; constructor(private readonly configurationService: SimpleConfigurationService) { this.configurationService.onDidChangeConfiguration((e) => { - this._onDidChangeConfigurationEmitter.fire(e); + this._onDidChangeConfiguration.fire(e); }); } @@ -519,7 +522,7 @@ export class SimpleResourcePropertiesService implements ITextResourcePropertiesS } export class StandaloneTelemetryService implements ITelemetryService { - _serviceBrand: void; + _serviceBrand: void = undefined; public isOptedIn = false; @@ -687,7 +690,7 @@ export class SimpleLayoutService implements ILayoutService { public onLayout = Event.None; - private _dimension: IDimension; + private _dimension?: IDimension; get dimension(): IDimension { if (!this._dimension) { this._dimension = dom.getClientArea(window.document.body); diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 532eb6fdf7..2172471a22 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -153,7 +153,7 @@ function createAriaDomNode() { */ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandaloneCodeEditor { - private readonly _standaloneKeybindingService: StandaloneKeybindingService; + private readonly _standaloneKeybindingService: StandaloneKeybindingService | null; constructor( domElement: HTMLElement, @@ -178,6 +178,8 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon if (keybindingService instanceof StandaloneKeybindingService) { this._standaloneKeybindingService = keybindingService; + } else { + this._standaloneKeybindingService = null; } // Create the ARIA dom node as soon as the first editor is instantiated diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index 1dc3b704b9..385f8dcf67 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -482,6 +482,20 @@ export function registerFoldingRangeProvider(languageId: string, provider: modes return modes.FoldingRangeProviderRegistry.register(languageId, provider); } +/** + * Register a declaration provider + */ +export function registerDeclarationProvider(languageId: string, provider: modes.DeclarationProvider): IDisposable { + return modes.DeclarationProviderRegistry.register(languageId, provider); +} + +/** + * Register a selection range provider + */ +export function registerSelectionRangeProvider(languageId: string, provider: modes.SelectionRangeProvider): IDisposable { + return modes.SelectionRangeRegistry.register(languageId, provider); +} + /** * Contains additional diagnostic information about the context in which * a [code action](#CodeActionProvider.provideCodeActions) is run. @@ -542,6 +556,8 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { registerLinkProvider: registerLinkProvider, registerColorProvider: registerColorProvider, registerFoldingRangeProvider: registerFoldingRangeProvider, + registerDeclarationProvider: registerDeclarationProvider, + registerSelectionRangeProvider: registerSelectionRangeProvider, // enums DocumentHighlightKind: standaloneEnums.DocumentHighlightKind, diff --git a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts index a81c4c3d0f..717f5a0d84 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts @@ -160,7 +160,7 @@ export class StandaloneThemeServiceImpl implements IStandaloneThemeService { private readonly _knownThemes: Map; private readonly _styleElement: HTMLStyleElement; - private _theme: IStandaloneTheme; + private _theme!: IStandaloneTheme; private readonly _onThemeChange: Emitter; private readonly _onIconThemeChange: Emitter; private readonly environment: IEnvironmentService = Object.create(null); diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index f49f80b3c6..697edbffec 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -3633,6 +3633,75 @@ suite('Editor Controller - Indentation Rules', () => { model.dispose(); mode.dispose(); }); + + test('', () => { + class JSONMode extends MockMode { + private static readonly _id = new LanguageIdentifier('indentRulesMode', 4); + constructor() { + super(JSONMode._id); + this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), { + brackets: [ + ['{', '}'], + ['[', ']'], + ['(', ')'] + ], + indentationRules: { + increaseIndentPattern: new RegExp('^.*\\{[^}\"\\\']*$|^.*\\([^\\)\"\\\']*$|^\\s*(public|private|protected):\\s*$|^\\s*@(public|private|protected)\\s*$|^\\s*\\{\\}$'), + decreaseIndentPattern: new RegExp('^\\s*(\\s*/[*].*[*]/\\s*)*\\}|^\\s*(\\s*/[*].*[*]/\\s*)*\\)|^\\s*(public|private|protected):\\s*$|^\\s*@(public|private|protected)\\s*$'), + } + })); + } + } + + let mode = new JSONMode(); + let model = createTextModel( + [ + '{', + ' "scripts: {"', + ' "watch": "a {"', + ' "build{": "b"', + ' "tasks": []', + ' "tasks": ["a"]', + ' "}"', + '"}"' + ].join('\n'), + { + tabSize: 2, + indentSize: 2 + }, + mode.getLanguageIdentifier() + ); + + withTestCodeEditor(null, { model: model, autoIndent: true }, (editor, cursor) => { + moveTo(cursor, 3, 19, false); + assertCursor(cursor, new Selection(3, 19, 3, 19)); + + cursorCommand(cursor, H.Type, { text: '\n' }, 'keyboard'); + assert.deepEqual(model.getLineContent(4), ' '); + + moveTo(cursor, 5, 18, false); + assertCursor(cursor, new Selection(5, 18, 5, 18)); + + cursorCommand(cursor, H.Type, { text: '\n' }, 'keyboard'); + assert.deepEqual(model.getLineContent(6), ' '); + + moveTo(cursor, 7, 15, false); + assertCursor(cursor, new Selection(7, 15, 7, 15)); + + cursorCommand(cursor, H.Type, { text: '\n' }, 'keyboard'); + assert.deepEqual(model.getLineContent(8), ' '); + assert.deepEqual(model.getLineContent(9), ' ]'); + + moveTo(cursor, 10, 18, false); + assertCursor(cursor, new Selection(10, 18, 10, 18)); + + cursorCommand(cursor, H.Type, { text: '\n' }, 'keyboard'); + assert.deepEqual(model.getLineContent(11), ' ]'); + }); + + model.dispose(); + mode.dispose(); + }); }); interface ICursorOpts { @@ -4595,6 +4664,34 @@ suite('autoClosingPairs', () => { mode.dispose(); }); + test('issue #78527 - does not close quote on odd count', () => { + let mode = new AutoClosingMode(); + usingCursor({ + text: [ + 'std::cout << \'"\' << entryMap' + ], + languageIdentifier: mode.getLanguageIdentifier() + }, (model, cursor) => { + cursor.setSelections('test', [new Selection(1, 29, 1, 29)]); + + cursorCommand(cursor, H.Type, { text: '[' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'std::cout << \'"\' << entryMap[]'); + + cursorCommand(cursor, H.Type, { text: '"' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'std::cout << \'"\' << entryMap[""]'); + + cursorCommand(cursor, H.Type, { text: 'a' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'std::cout << \'"\' << entryMap["a"]'); + + cursorCommand(cursor, H.Type, { text: '"' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'std::cout << \'"\' << entryMap["a"]'); + + cursorCommand(cursor, H.Type, { text: ']' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'std::cout << \'"\' << entryMap["a"]'); + }); + mode.dispose(); + }); + test('issue #15825: accents on mac US intl keyboard', () => { let mode = new AutoClosingMode(); usingCursor({ diff --git a/src/vs/editor/test/common/model/textModel.test.ts b/src/vs/editor/test/common/model/textModel.test.ts index e244a529da..255953c175 100644 --- a/src/vs/editor/test/common/model/textModel.test.ts +++ b/src/vs/editor/test/common/model/textModel.test.ts @@ -608,6 +608,46 @@ suite('Editor Model - TextModel', () => { ]); }); + test('issue #62143: Broken indentation detection', () => { + // works before the fix + assertGuess(true, 2, [ + 'x', + 'x', + ' x', + ' x' + ]); + + // works before the fix + assertGuess(true, 2, [ + 'x', + ' - item2', + ' - item3' + ]); + + // works before the fix + testGuessIndentation(true, 2, true, 2, [ + 'x x', + ' x', + ' x', + ]); + + // fails before the fix + // empty space inline breaks the indentation guess + testGuessIndentation(true, 2, true, 2, [ + 'x x', + ' x', + ' x', + ' x' + ]); + + testGuessIndentation(true, 2, true, 2, [ + '', + '- item1', + ' - item2', + ' - item3' + ]); + }); + test('validatePosition', () => { let m = TextModel.createFromString('line one\nline two'); diff --git a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts index ddae0c77b2..3a4b114338 100644 --- a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts +++ b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts @@ -109,9 +109,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => { [ '
', 'Ciao', - ' ', + ' ', 'hello', - ' ', + ' ', 'world!', '
' ].join('') @@ -122,9 +122,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => { [ '
', 'Ciao', - ' ', + ' ', 'hello', - ' ', + ' ', 'w', '
' ].join('') @@ -135,9 +135,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => { [ '
', 'Ciao', - ' ', + ' ', 'hello', - ' ', + ' ', '
' ].join('') ); @@ -147,9 +147,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => { [ '
', 'iao', - ' ', + ' ', 'hello', - ' ', + ' ', '
' ].join('') ); @@ -158,9 +158,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => { tokenizeLineToHTML(text, lineTokens, colorMap, 4, 11, 4), [ '
', - ' ', + ' ', 'hello', - ' ', + ' ', '
' ].join('') ); @@ -170,7 +170,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { [ '
', 'hello', - ' ', + ' ', '
' ].join('') ); @@ -193,6 +193,88 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ].join('') ); }); + test('tokenizeLineToHTML handle spaces #35954', () => { + const text = ' Ciao hello world!'; + const lineTokens = new ViewLineTokens([ + new ViewLineToken( + 2, + ( + (1 << MetadataConsts.FOREGROUND_OFFSET) + ) >>> 0 + ), + new ViewLineToken( + 6, + ( + (3 << MetadataConsts.FOREGROUND_OFFSET) + | ((FontStyle.Bold | FontStyle.Italic) << MetadataConsts.FONT_STYLE_OFFSET) + ) >>> 0 + ), + new ViewLineToken( + 9, + ( + (1 << MetadataConsts.FOREGROUND_OFFSET) + ) >>> 0 + ), + new ViewLineToken( + 14, + ( + (4 << MetadataConsts.FOREGROUND_OFFSET) + ) >>> 0 + ), + new ViewLineToken( + 15, + ( + (1 << MetadataConsts.FOREGROUND_OFFSET) + ) >>> 0 + ), + new ViewLineToken( + 21, + ( + (5 << MetadataConsts.FOREGROUND_OFFSET) + | ((FontStyle.Underline) << MetadataConsts.FONT_STYLE_OFFSET) + ) >>> 0 + ) + ]); + const colorMap = [null!, '#000000', '#ffffff', '#ff0000', '#00ff00', '#0000ff']; + + assert.equal( + tokenizeLineToHTML(text, lineTokens, colorMap, 0, 21, 4), + [ + '
', + '  ', + 'Ciao', + '   ', + 'hello', + ' ', + 'world!', + '
' + ].join('') + ); + + assert.equal( + tokenizeLineToHTML(text, lineTokens, colorMap, 0, 17, 4), + [ + '
', + '  ', + 'Ciao', + '   ', + 'hello', + ' ', + 'wo', + '
' + ].join('') + ); + + assert.equal( + tokenizeLineToHTML(text, lineTokens, colorMap, 0, 3, 4), + [ + '
', + '  ', + 'C', + '
' + ].join('') + ); + }); }); diff --git a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts index 815a3e5d2b..0ee91485ba 100644 --- a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts +++ b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts @@ -114,6 +114,11 @@ suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => { assert.equal(mapper!.getWrappedLinesIndent(), ' \t'); }); + test('issue #75494: surrogate pairs', () => { + let factory = new CharacterHardWrappingLineMapperFactory('', ' ', ''); + assertLineMapping(factory, 4, 49, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇|👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬', WrappingIndent.Same); + }); + test('CharacterHardWrappingLineMapper - WrappingIndent.DeepIndent', () => { let factory = new CharacterHardWrappingLineMapperFactory('', ' ', ''); let mapper = assertLineMapping(factory, 4, 26, ' W e A r e T e s t |i n g D e |e p I n d |e n t a t |i o n', WrappingIndent.DeepIndent); diff --git a/src/vs/loader.js b/src/vs/loader.js index faff04a87e..d522e400fb 100644 --- a/src/vs/loader.js +++ b/src/vs/loader.js @@ -709,8 +709,11 @@ var AMDLoader; var recorder = moduleManager.getRecorder(); var cachedDataPath = that._getCachedDataPath(nodeCachedData, filename); var options = { filename: filename }; + var hashData; try { - options.cachedData = that._fs.readFileSync(cachedDataPath); + var data = that._fs.readFileSync(cachedDataPath); + hashData = data.slice(0, 16); + options.cachedData = data.slice(16); recorder.record(60 /* CachedDataFound */, cachedDataPath); } catch (_e) { @@ -724,7 +727,8 @@ var AMDLoader; var args = [this.exports, require, this, filename, dirname, process, _commonjsGlobal, Buffer]; var result = compileWrapper.apply(this.exports, args); // cached data aftermath - setTimeout(function () { return that._handleCachedData(script, cachedDataPath, !options.cachedData, moduleManager); }, Math.ceil(moduleManager.getConfig().getOptionsLiteral().nodeCachedData.writeDelay * Math.random())); + that._handleCachedData(script, scriptSource, cachedDataPath, !options.cachedData, moduleManager); + that._verifyCachedData(script, scriptSource, cachedDataPath, hashData); return result; }; }; @@ -755,7 +759,7 @@ var AMDLoader; var vmScriptPathOrUri_1 = this._getElectronRendererScriptPathOrUri(normalizedScriptSrc_1); var wantsCachedData_1 = Boolean(opts.nodeCachedData); var cachedDataPath_1 = wantsCachedData_1 ? this._getCachedDataPath(opts.nodeCachedData, scriptSrc) : undefined; - this._readSourceAndCachedData(normalizedScriptSrc_1, cachedDataPath_1, recorder, function (err, data, cachedData) { + this._readSourceAndCachedData(normalizedScriptSrc_1, cachedDataPath_1, recorder, function (err, data, cachedData, hashData) { if (err) { errorback(err); return; @@ -770,7 +774,8 @@ var AMDLoader; scriptSource = nodeInstrumenter(scriptSource, normalizedScriptSrc_1); var scriptOpts = { filename: vmScriptPathOrUri_1, cachedData: cachedData }; var script = _this._createAndEvalScript(moduleManager, scriptSource, scriptOpts, callback, errorback); - _this._handleCachedData(script, cachedDataPath_1, wantsCachedData_1 && !cachedData, moduleManager); + _this._handleCachedData(script, scriptSource, cachedDataPath_1, wantsCachedData_1 && !cachedData, moduleManager); + _this._verifyCachedData(script, scriptSource, cachedDataPath_1, hashData); }); } }; @@ -815,13 +820,13 @@ var AMDLoader; var basename = this._path.basename(filename).replace(/\.js$/, ''); return this._path.join(config.path, basename + "-" + hash + ".code"); }; - NodeScriptLoader.prototype._handleCachedData = function (script, cachedDataPath, createCachedData, moduleManager) { + NodeScriptLoader.prototype._handleCachedData = function (script, scriptSource, cachedDataPath, createCachedData, moduleManager) { var _this = this; if (script.cachedDataRejected) { // cached data got rejected -> delete and re-create this._fs.unlink(cachedDataPath, function (err) { moduleManager.getRecorder().record(62 /* CachedDataRejected */, cachedDataPath); - _this._createAndWriteCachedData(script, cachedDataPath, moduleManager); + _this._createAndWriteCachedData(script, scriptSource, cachedDataPath, moduleManager); if (err) { moduleManager.getConfig().onError(err); } @@ -829,22 +834,29 @@ var AMDLoader; } else if (createCachedData) { // no cached data, but wanted - this._createAndWriteCachedData(script, cachedDataPath, moduleManager); + this._createAndWriteCachedData(script, scriptSource, cachedDataPath, moduleManager); } }; - NodeScriptLoader.prototype._createAndWriteCachedData = function (script, cachedDataPath, moduleManager) { + // Cached data format: | SOURCE_HASH | V8_CACHED_DATA | + // -SOURCE_HASH is the md5 hash of the JS source (always 16 bytes) + // -V8_CACHED_DATA is what v8 produces + NodeScriptLoader.prototype._createAndWriteCachedData = function (script, scriptSource, cachedDataPath, moduleManager) { var _this = this; var timeout = Math.ceil(moduleManager.getConfig().getOptionsLiteral().nodeCachedData.writeDelay * (1 + Math.random())); var lastSize = -1; var iteration = 0; + var hashData = undefined; var createLoop = function () { setTimeout(function () { + if (!hashData) { + hashData = _this._crypto.createHash('md5').update(scriptSource, 'utf8').digest(); + } var cachedData = script.createCachedData(); if (cachedData.length === 0 || cachedData.length === lastSize || iteration >= 5) { return; } lastSize = cachedData.length; - _this._fs.writeFile(cachedDataPath, cachedData, function (err) { + _this._fs.writeFile(cachedDataPath, Buffer.concat([hashData, cachedData]), function (err) { if (err) { moduleManager.getConfig().onError(err); } @@ -865,15 +877,16 @@ var AMDLoader; } else { // cached data case: read both files in parallel - var source_1; - var cachedData_1; + var source_1 = undefined; + var cachedData_1 = undefined; + var hashData_1 = undefined; var steps_1 = 2; var step_1 = function (err) { if (err) { callback(err); } else if (--steps_1 === 0) { - callback(undefined, source_1, cachedData_1); + callback(undefined, source_1, cachedData_1, hashData_1); } }; this._fs.readFile(sourcePath, { encoding: 'utf8' }, function (err, data) { @@ -881,12 +894,39 @@ var AMDLoader; step_1(err); }); this._fs.readFile(cachedDataPath, function (err, data) { - cachedData_1 = data && data.length > 0 ? data : undefined; + if (!err && data && data.length > 0) { + hashData_1 = data.slice(0, 16); + cachedData_1 = data.slice(16); + recorder.record(60 /* CachedDataFound */, cachedDataPath); + } + else { + recorder.record(61 /* CachedDataMissed */, cachedDataPath); + } step_1(); // ignored: cached data is optional - recorder.record(err ? 61 /* CachedDataMissed */ : 60 /* CachedDataFound */, cachedDataPath); }); } }; + NodeScriptLoader.prototype._verifyCachedData = function (script, scriptSource, cachedDataPath, hashData) { + var _this = this; + if (!hashData) { + // nothing to do + return; + } + if (script.cachedDataRejected) { + // invalid anyways + return; + } + setTimeout(function () { + // check source hash - the contract is that file paths change when file content + // change (e.g use the commit or version id as cache path). this check is + // for violations of this contract. + var hashDataNow = _this._crypto.createHash('md5').update(scriptSource, 'utf8').digest(); + if (!hashData.equals(hashDataNow)) { + console.warn("FAILED TO VERIFY CACHED DATA. Deleting '" + cachedDataPath + "' now, but a RESTART IS REQUIRED"); + _this._fs.unlink(cachedDataPath, function (err) { return console.error("FAILED to unlink: '" + cachedDataPath + "'", err); }); + } + }, Math.ceil(5000 * (1 + Math.random()))); + }; NodeScriptLoader._BOM = 0xFEFF; NodeScriptLoader._PREFIX = '(function (require, define, __filename, __dirname) { '; NodeScriptLoader._SUFFIX = '\n});'; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 66893ff470..43d11160fb 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2649,6 +2649,11 @@ declare namespace monaco.editor { * Defaults to true. */ lineNumbers?: 'on' | 'off' | 'relative' | 'interval' | ((lineNumber: number) => string); + /** + * Controls the minimal number of visible leading and trailing lines surrounding the cursor. + * Defaults to 0. + */ + cursorSurroundingLines?: number; /** * Render last line number when the file ends with a newline. * Defaults to true. @@ -3301,6 +3306,7 @@ declare namespace monaco.editor { readonly ariaLabel: string; readonly renderLineNumbers: RenderLineNumbersType; readonly renderCustomLineNumbers: ((lineNumber: number) => string) | null; + readonly cursorSurroundingLines: number; readonly renderFinalNewline: boolean; readonly selectOnLineNumbers: boolean; readonly glyphMargin: boolean; @@ -4453,6 +4459,16 @@ declare namespace monaco.languages { */ export function registerFoldingRangeProvider(languageId: string, provider: FoldingRangeProvider): IDisposable; + /** + * Register a declaration provider + */ + export function registerDeclarationProvider(languageId: string, provider: DeclarationProvider): IDisposable; + + /** + * Register a selection range provider + */ + export function registerSelectionRangeProvider(languageId: string, provider: SelectionRangeProvider): IDisposable; + /** * Contains additional diagnostic information about the context in which * a [code action](#CodeActionProvider.provideCodeActions) is run. diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 8201b72544..c71ce227c4 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -21,7 +21,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati class AlternativeKeyEmitter extends Emitter { private readonly _subscriptions = new DisposableStore(); - private _isPressed: boolean; + private _isPressed: boolean = false; private static instance: AlternativeKeyEmitter; private _suppressAltKeyUp: boolean = false; @@ -138,7 +138,7 @@ export class MenuEntryActionViewItem extends ActionViewItem { static readonly ICON_PATH_TO_CSS_RULES: Map = new Map(); - private _wantsAltCommand: boolean; + private _wantsAltCommand: boolean = false; private readonly _itemClassDispose = this._register(new MutableDisposable()); private readonly _altKey: AlternativeKeyEmitter; diff --git a/src/vs/platform/actions/common/menuService.ts b/src/vs/platform/actions/common/menuService.ts index 25945714e7..e1f5cdb548 100644 --- a/src/vs/platform/actions/common/menuService.ts +++ b/src/vs/platform/actions/common/menuService.ts @@ -31,8 +31,8 @@ class Menu extends Disposable implements IMenu { private readonly _onDidChange = this._register(new Emitter()); - private _menuGroups: MenuItemGroup[]; - private _contextKeys: Set; + private _menuGroups!: MenuItemGroup[]; + private _contextKeys!: Set; constructor( private readonly _id: MenuId, diff --git a/src/vs/platform/clipboard/browser/clipboardService.ts b/src/vs/platform/clipboard/browser/clipboardService.ts index 25e805628f..ecb5a9279d 100644 --- a/src/vs/platform/clipboard/browser/clipboardService.ts +++ b/src/vs/platform/clipboard/browser/clipboardService.ts @@ -10,7 +10,7 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export class BrowserClipboardService implements IClipboardService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private _internalResourcesClipboard: URI[] | undefined; @@ -46,4 +46,4 @@ export class BrowserClipboardService implements IClipboardService { } } -registerSingleton(IClipboardService, BrowserClipboardService, true); \ No newline at end of file +registerSingleton(IClipboardService, BrowserClipboardService, true); diff --git a/src/vs/platform/diagnostics/common/diagnosticsService.ts b/src/vs/platform/diagnostics/common/diagnosticsService.ts index b9e7450df3..678c7e7a09 100644 --- a/src/vs/platform/diagnostics/common/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/common/diagnosticsService.ts @@ -63,6 +63,10 @@ export interface PerformanceInfo { workspaceInfo?: string; } +export interface IWorkspaceInformation extends IWorkspace { + telemetryId: string | undefined; +} + export const ID = 'diagnosticsService'; export const IDiagnosticsService = createDecorator(ID); @@ -72,7 +76,7 @@ export interface IDiagnosticsService { getPerformanceInfo(mainProcessInfo: IMainProcessInfo, remoteInfo: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise; getSystemInfo(mainProcessInfo: IMainProcessInfo, remoteInfo: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise; getDiagnostics(mainProcessInfo: IMainProcessInfo, remoteInfo: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise; - reportWorkspaceStats(workspace: IWorkspace): Promise; + reportWorkspaceStats(workspace: IWorkspaceInformation): Promise; } export function isRemoteDiagnosticError(x: any): x is IRemoteDiagnosticError { diff --git a/src/vs/platform/diagnostics/node/diagnosticsIpc.ts b/src/vs/platform/diagnostics/node/diagnosticsIpc.ts index baa5b6b7be..f5699bab33 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsIpc.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsIpc.ts @@ -36,7 +36,7 @@ export class DiagnosticsChannel implements IServerChannel { export class DiagnosticsService implements IDiagnosticsService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; constructor(private channel: IChannel) { } @@ -55,4 +55,4 @@ export class DiagnosticsService implements IDiagnosticsService { public reportWorkspaceStats(workspace: IWorkspace): Promise { return this.channel.call('reportWorkspaceStats', workspace); } -} \ No newline at end of file +} diff --git a/src/vs/platform/diagnostics/node/diagnosticsService.ts b/src/vs/platform/diagnostics/node/diagnosticsService.ts index ecd08b4a35..b3a90f9eaf 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as osLib from 'os'; import { virtualMachineHint } from 'vs/base/node/id'; -import { IMachineInfo, WorkspaceStats, WorkspaceStatItem, IDiagnosticsService, PerformanceInfo, SystemInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { IMachineInfo, WorkspaceStats, WorkspaceStatItem, IDiagnosticsService, PerformanceInfo, SystemInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError, isRemoteDiagnosticError, IWorkspaceInformation } from 'vs/platform/diagnostics/common/diagnosticsService'; import { readdir, stat, exists, readFile } from 'fs'; import { join, basename } from 'vs/base/common/path'; import { parse, ParseError } from 'vs/base/common/json'; @@ -16,7 +16,6 @@ import { isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { ProcessItem } from 'vs/base/common/processes'; import { IMainProcessInfo } from 'vs/platform/launch/common/launchService'; -import { IWorkspace } from 'vs/platform/workspace/common/workspace'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export interface VersionInfo { @@ -514,7 +513,7 @@ export class DiagnosticsService implements IDiagnosticsService { } } - public async reportWorkspaceStats(workspace: IWorkspace): Promise { + public async reportWorkspaceStats(workspace: IWorkspaceInformation): Promise { workspace.folders.forEach(folder => { const folderUri = URI.revive(folder.uri); if (folderUri.scheme === 'file') { @@ -525,16 +524,19 @@ export class DiagnosticsService implements IDiagnosticsService { count: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; }; type WorkspaceStatsClassification = { + 'workspace.id': { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; fileTypes: WorkspaceStatItemClassification; configTypes: WorkspaceStatItemClassification; launchConfigs: WorkspaceStatItemClassification; }; type WorkspaceStatsEvent = { + 'workspace.id': string | undefined; fileTypes: WorkspaceStatItem[]; configTypes: WorkspaceStatItem[]; launchConfigs: WorkspaceStatItem[]; }; this.telemetryService.publicLog2('workspace.stats', { + 'workspace.id': workspace.telemetryId, fileTypes: stats.fileTypes, configTypes: stats.configFiles, launchConfigs: stats.launchConfigFiles @@ -545,4 +547,4 @@ export class DiagnosticsService implements IDiagnosticsService { } }); } -} \ No newline at end of file +} diff --git a/src/vs/platform/dialogs/browser/dialogService.ts b/src/vs/platform/dialogs/browser/dialogService.ts index 465a5039cb..2763fc059f 100644 --- a/src/vs/platform/dialogs/browser/dialogService.ts +++ b/src/vs/platform/dialogs/browser/dialogService.ts @@ -40,32 +40,35 @@ export class DialogService implements IDialogService { buttons.push(nls.localize('cancelButton', "Cancel")); } - const severity = this.getSeverity(confirmation.type || 'none'); - const result = await this.show(severity, confirmation.message, buttons, { cancelId: 1, detail: confirmation.detail }); + const dialogDisposables = new DisposableStore(); + const dialog = new Dialog( + this.layoutService.container, + confirmation.message, + buttons, + { + detail: confirmation.detail, + cancelId: 1, + type: confirmation.type, + keyEventProcessor: (event: StandardKeyboardEvent) => { + EventHelper.stop(event, true); + }, + checkboxChecked: confirmation.checkbox ? confirmation.checkbox.checked : undefined, + checkboxLabel: confirmation.checkbox ? confirmation.checkbox.label : undefined + }); - return { confirmed: result === 0 }; - } + dialogDisposables.add(dialog); + dialogDisposables.add(attachDialogStyler(dialog, this.themeService)); - private getSeverity(type: DialogType): Severity { - switch (type) { - case 'error': - return Severity.Error; - case 'warning': - return Severity.Warning; - case 'question': - case 'info': - return Severity.Info; - case 'none': - default: - return Severity.Ignore; - } + const result = await dialog.show(); + dialogDisposables.dispose(); + + return { confirmed: result.button === 0, checkboxChecked: result.checkboxChecked }; } private getDialogType(severity: Severity): DialogType { return (severity === Severity.Info) ? 'question' : (severity === Severity.Error) ? 'error' : (severity === Severity.Warning) ? 'warning' : 'none'; } - async show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise { this.logService.trace('DialogService#show', message); @@ -86,9 +89,9 @@ export class DialogService implements IDialogService { dialogDisposables.add(dialog); dialogDisposables.add(attachDialogStyler(dialog, this.themeService)); - const choice = await dialog.show(); + const result = await dialog.show(); dialogDisposables.dispose(); - return choice; + return result.button; } } diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index ea8ced129a..6351b1ba57 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -127,6 +127,8 @@ export const IDialogService = createDecorator('dialogService'); export interface IDialogOptions { cancelId?: number; detail?: string; + checkboxLabel?: string; + checkboxChecked?: boolean; } /** @@ -234,4 +236,4 @@ export function getConfirmMessage(start: string, resourcesToConfirm: URI[]): str message.push(''); return message.join('\n'); -} \ No newline at end of file +} diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index c84fb693e8..29c5ead3f4 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; export interface ParsedArgs { @@ -38,6 +38,7 @@ export interface ParsedArgs { 'builtin-extensions-dir'?: string; extensionDevelopmentPath?: string | string[]; // one or more local paths or URIs extensionTestsPath?: string; // either a local path or a URI + 'extension-development-confirm-save'?: boolean; 'inspect-extensions'?: string; 'inspect-brk-extensions'?: string; debugId?: string; @@ -105,7 +106,8 @@ export interface IExtensionHostDebugParams extends IDebugParams { export const BACKUPS = 'Backups'; export interface IEnvironmentService { - _serviceBrand: any; + + _serviceBrand: ServiceIdentifier; args: ParsedArgs; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index fb94ffa0ec..791c2ebe53 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as minimist from 'minimist'; +import * as minimist from 'vscode-minimist'; import * as os from 'os'; import { localize } from 'vs/nls'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; @@ -69,6 +69,7 @@ export const options: Option[] = [ { id: 'locate-extension', type: 'string' }, { id: 'extensionDevelopmentPath', type: 'string' }, { id: 'extensionTestsPath', type: 'string' }, + { id: 'extension-development-confirm-save', type: 'boolean' }, { id: 'debugId', type: 'string' }, { id: 'inspect-search', type: 'string', deprecates: 'debugSearch' }, { id: 'inspect-brk-search', type: 'string', deprecates: 'debugBrkSearch' }, @@ -141,6 +142,10 @@ export function parseArgs(args: string[], isOptionSupported = (_: Option) => tru delete parsedArgs[o.deprecates]; } } + + // https://github.com/microsoft/vscode/issues/58177 + parsedArgs._ = parsedArgs._.filter(arg => arg.length > 0); + return parsedArgs; } diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index c9e85ebc17..9421af4537 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -16,6 +16,7 @@ import { toLocalISOString } from 'vs/base/common/date'; import { isWindows, isLinux } from 'vs/base/common/platform'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { URI } from 'vs/base/common/uri'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; // Read this before there's any chance it is overwritten // Related to https://github.com/Microsoft/vscode/issues/30624 @@ -76,7 +77,7 @@ function getCLIPath(execPath: string, appRoot: string, isBuilt: boolean): string export class EnvironmentService implements IEnvironmentService { - _serviceBrand: any; + _serviceBrand!: ServiceIdentifier; get args(): ParsedArgs { return this._args; } diff --git a/src/vs/platform/environment/test/node/environmentService.test.ts b/src/vs/platform/environment/test/node/environmentService.test.ts index 56afce09ed..13b60e4273 100644 --- a/src/vs/platform/environment/test/node/environmentService.test.ts +++ b/src/vs/platform/environment/test/node/environmentService.test.ts @@ -52,4 +52,15 @@ suite('EnvironmentService', () => { assert.equal(parse(['--user-data-dir', './dir'], { cwd: () => '/foo', env: { 'VSCODE_CWD': '/bar' } }), path.resolve('/bar/dir'), 'should use VSCODE_CWD as the cwd when --user-data-dir is specified'); }); + + // https://github.com/microsoft/vscode/issues/78440 + test('careful with boolean file names', function () { + let actual = parseArgs(['-r', 'arg.txt']); + assert(actual['reuse-window']); + assert.deepEqual(actual._, ['arg.txt']); + + actual = parseArgs(['-r', 'true.txt']); + assert(actual['reuse-window']); + assert.deepEqual(actual._, ['true.txt']); + }); }); diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index 2ad6b312b4..530a97ffc9 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -21,7 +21,7 @@ import { Schemas } from 'vs/base/common/network'; export class FileService extends Disposable implements IFileService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private readonly BUFFER_SIZE = 64 * 1024; @@ -164,21 +164,25 @@ export class FileService extends Disposable implements IFileService { private async doResolveFile(resource: URI, options?: IResolveFileOptions): Promise { const provider = await this.withProvider(resource); - // leverage a trie to check for recursive resolving const resolveTo = options && options.resolveTo; - const trie = TernarySearchTree.forPaths(); - trie.set(resource.toString(), true); - if (isNonEmptyArray(resolveTo)) { - resolveTo.forEach(uri => trie.set(uri.toString(), true)); - } - const resolveSingleChildDescendants = !!(options && options.resolveSingleChildDescendants); const resolveMetadata = !!(options && options.resolveMetadata); const stat = await provider.stat(resource); + let trie: TernarySearchTree | undefined; + return this.toFileStat(provider, resource, stat, undefined, resolveMetadata, (stat, siblings) => { + // lazy trie to check for recursive resolving + if (!trie) { + trie = TernarySearchTree.forPaths(); + trie.set(resource.toString(), true); + if (isNonEmptyArray(resolveTo)) { + resolveTo.forEach(uri => trie!.set(uri.toString(), true)); + } + } + // check for recursive resolving if (Boolean(trie.findSuperstr(stat.resource.toString()) || trie.get(stat.resource.toString()))) { return true; @@ -253,8 +257,12 @@ export class FileService extends Disposable implements IFileService { } async exists(resource: URI): Promise { + const provider = await this.withProvider(resource); + try { - return !!(await this.resolve(resource)); + const stat = await provider.stat(resource); + + return !!stat; } catch (error) { return false; } diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index 8aa88c3b2a..f9f910af41 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, Dirent, Stats } from 'fs'; +import { mkdir, open, close, read, write, fdatasync } from 'fs'; import { promisify } from 'util'; import { IDisposable, Disposable, toDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; import { IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, FileSystemProviderErrorCode, createFileSystemProviderError, FileSystemProviderError } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { isLinux, isWindows } from 'vs/base/common/platform'; -import { statLink, unlink, move, copy, readFile, truncate, rimraf, RimRafMode, exists, readdirWithFileTypes } from 'vs/base/node/pfs'; +import { statLink, readdir, unlink, move, copy, readFile, truncate, rimraf, RimRafMode, exists } from 'vs/base/node/pfs'; import { normalize, basename, dirname } from 'vs/base/common/path'; import { joinPath } from 'vs/base/common/resources'; import { isEqual } from 'vs/base/common/extpath'; @@ -62,8 +62,15 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro try { const { stat, isSymbolicLink } = await statLink(this.toFilePath(resource)); // cannot use fs.stat() here to support links properly + let type: number; + if (isSymbolicLink) { + type = FileType.SymbolicLink | (stat.isDirectory() ? FileType.Directory : FileType.File); + } else { + type = stat.isFile() ? FileType.File : stat.isDirectory() ? FileType.Directory : FileType.Unknown; + } + return { - type: this.toType(stat, isSymbolicLink), + type, ctime: stat.ctime.getTime(), mtime: stat.mtime.getTime(), size: stat.size @@ -75,19 +82,13 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro async readdir(resource: URI): Promise<[string, FileType][]> { try { - const children = await readdirWithFileTypes(this.toFilePath(resource)); + const children = await readdir(this.toFilePath(resource)); const result: [string, FileType][] = []; await Promise.all(children.map(async child => { try { - let type: FileType; - if (child.isSymbolicLink()) { - type = (await this.stat(joinPath(resource, child.name))).type; // always resolve target the link points to if any - } else { - type = this.toType(child); - } - - result.push([child.name, type]); + const stat = await this.stat(joinPath(resource, child)); + result.push([child, stat.type]); } catch (error) { this.logService.trace(error); // ignore errors for individual entries that can arise from permission denied } @@ -99,14 +100,6 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro } } - private toType(entry: Stats | Dirent, isSymbolicLink = entry.isSymbolicLink()): FileType { - if (isSymbolicLink) { - return FileType.SymbolicLink | (entry.isDirectory() ? FileType.Directory : FileType.File); - } - - return entry.isFile() ? FileType.File : entry.isDirectory() ? FileType.Directory : FileType.Unknown; - } - //#endregion //#region File Reading/Writing @@ -148,6 +141,8 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro } } + private mapHandleToPos: Map = new Map(); + private writeHandles: Set = new Set(); private canFlush: boolean = true; @@ -187,6 +182,13 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro const handle = await promisify(open)(filePath, flags); + // remember this handle to track file position of the handle + // we init the position to 0 since the file descriptor was + // just created and the position was not moved so far (see + // also http://man7.org/linux/man-pages/man2/open.2.html - + // "The file offset is set to the beginning of the file.") + this.mapHandleToPos.set(handle, 0); + // remember that this handle was used for writing if (opts.create) { this.writeHandles.add(handle); @@ -200,6 +202,10 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro async close(fd: number): Promise { try { + + // remove this handle from map of positions + this.mapHandleToPos.delete(fd); + // if a handle is closed that was used for writing, ensure // to flush the contents to disk if possible. if (this.writeHandles.delete(fd) && this.canFlush) { @@ -220,15 +226,81 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro } async read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { + const normalizedPos = this.normalizePos(fd, pos); + + let bytesRead: number | null = null; try { - const result = await promisify(read)(fd, data, offset, length, pos); + const result = await promisify(read)(fd, data, offset, length, normalizedPos); + if (typeof result === 'number') { - return result; // node.d.ts fail + bytesRead = result; // node.d.ts fail + } else { + bytesRead = result.bytesRead; } - return result.bytesRead; + return bytesRead; } catch (error) { throw this.toFileSystemProviderError(error); + } finally { + this.updatePos(fd, normalizedPos, bytesRead); + } + } + + private normalizePos(fd: number, pos: number): number | null { + + // when calling fs.read/write we try to avoid passing in the "pos" argument and + // rather prefer to pass in "null" because this avoids an extra seek(pos) + // call that in some cases can even fail (e.g. when opening a file over FTP - + // see https://github.com/microsoft/vscode/issues/73884). + // + // as such, we compare the passed in position argument with our last known + // position for the file descriptor and use "null" if they match. + if (pos === this.mapHandleToPos.get(fd)) { + return null; + } + + return pos; + } + + private updatePos(fd: number, pos: number | null, bytesLength: number | null): void { + const lastKnownPos = this.mapHandleToPos.get(fd); + if (typeof lastKnownPos === 'number') { + + // pos !== null signals that previously a position was used that is + // not null. node.js documentation explains, that in this case + // the internal file pointer is not moving and as such we do not move + // our position pointer. + // + // Docs: "If position is null, data will be read from the current file position, + // and the file position will be updated. If position is an integer, the file position + // will remain unchanged." + if (typeof pos === 'number') { + // do not modify the position + } + + // bytesLength = number is a signal that the read/write operation was + // successful and as such we need to advance the position in the Map + // + // Docs (http://man7.org/linux/man-pages/man2/read.2.html): + // "On files that support seeking, the read operation commences at the + // file offset, and the file offset is incremented by the number of + // bytes read." + // + // Docs (http://man7.org/linux/man-pages/man2/write.2.html): + // "For a seekable file (i.e., one to which lseek(2) may be applied, for + // example, a regular file) writing takes place at the file offset, and + // the file offset is incremented by the number of bytes actually + // written." + else if (typeof bytesLength === 'number') { + this.mapHandleToPos.set(fd, lastKnownPos + bytesLength); + } + + // bytesLength = null signals an error in the read/write operation + // and as such we drop the handle from the Map because the position + // is unspecificed at this point. + else { + this.mapHandleToPos.delete(fd); + } } } @@ -240,15 +312,23 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro } private async doWrite(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { + const normalizedPos = this.normalizePos(fd, pos); + + let bytesWritten: number | null = null; try { - const result = await promisify(write)(fd, data, offset, length, pos); + const result = await promisify(write)(fd, data, offset, length, normalizedPos); + if (typeof result === 'number') { - return result; // node.d.ts fail + bytesWritten = result; // node.d.ts fail + } else { + bytesWritten = result.bytesWritten; } - return result.bytesWritten; + return bytesWritten; } catch (error) { throw this.toFileSystemProviderError(error); + } finally { + this.updatePos(fd, normalizedPos, bytesWritten); } } @@ -441,14 +521,17 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro watcherOptions?: IWatcherOptions ): WindowsWatcherService | UnixWatcherService | NsfwWatcherService }; - let watcherOptions = undefined; + let watcherOptions: IWatcherOptions | undefined = undefined; + + // requires a polling watcher if (this.watcherOptions && this.watcherOptions.usePolling) { - // requires a polling watcher watcherImpl = UnixWatcherService; watcherOptions = this.watcherOptions; - } else { - // Single Folder Watcher + } + + // Single Folder Watcher + else { if (this.recursiveFoldersToWatch.length === 1) { if (isWindows) { watcherImpl = WindowsWatcherService; @@ -471,6 +554,7 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro if (msg.type === 'error') { this._onDidWatchErrorOccur.fire(msg.message); } + this.logService[msg.type](msg.message); }, this.logService.getLevel() === LogLevel.Trace, @@ -478,7 +562,7 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro ); if (!this.recursiveWatcherLogLevelListener) { - this.recursiveWatcherLogLevelListener = this.logService.onDidChangeLogLevel(_ => { + this.recursiveWatcherLogLevelListener = this.logService.onDidChangeLogLevel(() => { if (this.recursiveWatcher) { this.recursiveWatcher.setVerboseLogging(this.logService.getLevel() === LogLevel.Trace); } @@ -496,11 +580,13 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro if (msg.type === 'error') { this._onDidWatchErrorOccur.fire(msg.message); } + this.logService[msg.type](msg.message); }, this.logService.getLevel() === LogLevel.Trace ); - const logLevelListener = this.logService.onDidChangeLogLevel(_ => { + + const logLevelListener = this.logService.onDidChangeLogLevel(() => { watcherService.setVerboseLogging(this.logService.getLevel() === LogLevel.Trace); }); @@ -553,4 +639,4 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro dispose(this.recursiveWatcherLogLevelListener); this.recursiveWatcherLogLevelListener = undefined; } -} \ No newline at end of file +} diff --git a/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts b/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts index 88058ca05c..075c770507 100644 --- a/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts +++ b/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts @@ -18,7 +18,7 @@ export class OutOfProcessWin32FolderWatcher { private ignored: glob.ParsedPattern[]; - private handle: cp.ChildProcess; + private handle: cp.ChildProcess | undefined; private restartCounter: number; constructor( diff --git a/src/vs/platform/files/test/node/diskFileService.test.ts b/src/vs/platform/files/test/node/diskFileService.test.ts index 80915c453c..5ae6ce1ada 100644 --- a/src/vs/platform/files/test/node/diskFileService.test.ts +++ b/src/vs/platform/files/test/node/diskFileService.test.ts @@ -62,9 +62,9 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider { totalBytesRead: number = 0; - private invalidStatSize: boolean; + private invalidStatSize: boolean = false; - private _testCapabilities: FileSystemProviderCapabilities; + private _testCapabilities!: FileSystemProviderCapabilities; get capabilities(): FileSystemProviderCapabilities { if (!this._testCapabilities) { this._testCapabilities = @@ -115,7 +115,7 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider { } } -suite('Disk File Service', () => { +suite('Disk File Service', function () { const parentDir = getRandomTestPath(tmpdir(), 'vsctests', 'diskfileservice'); const testSchema = 'test'; @@ -127,6 +127,12 @@ suite('Disk File Service', () => { const disposables = new DisposableStore(); + // Given issues such as https://github.com/microsoft/vscode/issues/78602 + // we see random test failures when accessing the native file system. To + // diagnose further, we retry node.js file access tests up to 3 times to + // rule out any random disk issue. + this.retries(3); + setup(async () => { const logService = new NullLogService(); @@ -613,7 +619,7 @@ suite('Disk File Service', () => { await testMoveFolderAcrossProviders(); }); - test('move - directory - across providers (unbuffered => buffered)', async () => { + test('move - directory - across providers (unbuffered => buffered)', async function () { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); setCapabilities(testProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); @@ -1915,4 +1921,94 @@ suite('Disk File Service', () => { function hasChange(changes: IFileChange[], type: FileChangeType, resource: URI): boolean { return changes.some(change => change.type === type && isEqual(change.resource, resource)); } -}); \ No newline at end of file + + test('read - mixed positions', async () => { + const resource = URI.file(join(testDir, 'lorem.txt')); + + // read multiple times from position 0 + let buffer = VSBuffer.alloc(1024); + let fd = await fileProvider.open(resource, { create: false }); + for (let i = 0; i < 3; i++) { + await fileProvider.read(fd, 0, buffer.buffer, 0, 26); + assert.equal(buffer.slice(0, 26).toString(), 'Lorem ipsum dolor sit amet'); + } + await fileProvider.close(fd); + + // read multiple times at various locations + buffer = VSBuffer.alloc(1024); + fd = await fileProvider.open(resource, { create: false }); + + let posInFile = 0; + + await fileProvider.read(fd, posInFile, buffer.buffer, 0, 26); + assert.equal(buffer.slice(0, 26).toString(), 'Lorem ipsum dolor sit amet'); + posInFile += 26; + + await fileProvider.read(fd, posInFile, buffer.buffer, 0, 1); + assert.equal(buffer.slice(0, 1).toString(), ','); + posInFile += 1; + + await fileProvider.read(fd, posInFile, buffer.buffer, 0, 12); + assert.equal(buffer.slice(0, 12).toString(), ' consectetur'); + posInFile += 12; + + await fileProvider.read(fd, 98 /* no longer in sequence of posInFile */, buffer.buffer, 0, 9); + assert.equal(buffer.slice(0, 9).toString(), 'fermentum'); + + await fileProvider.read(fd, 27, buffer.buffer, 0, 12); + assert.equal(buffer.slice(0, 12).toString(), ' consectetur'); + + await fileProvider.read(fd, 26, buffer.buffer, 0, 1); + assert.equal(buffer.slice(0, 1).toString(), ','); + + await fileProvider.read(fd, 0, buffer.buffer, 0, 26); + assert.equal(buffer.slice(0, 26).toString(), 'Lorem ipsum dolor sit amet'); + + await fileProvider.read(fd, posInFile /* back in sequence */, buffer.buffer, 0, 11); + assert.equal(buffer.slice(0, 11).toString(), ' adipiscing'); + + await fileProvider.close(fd); + }); + + test('write - mixed positions', async () => { + const resource = URI.file(join(testDir, 'lorem.txt')); + + const buffer = VSBuffer.alloc(1024); + const fdWrite = await fileProvider.open(resource, { create: true }); + const fdRead = await fileProvider.open(resource, { create: false }); + + let posInFileWrite = 0; + let posInFileRead = 0; + + const initialContents = VSBuffer.fromString('Lorem ipsum dolor sit amet'); + await fileProvider.write(fdWrite, posInFileWrite, initialContents.buffer, 0, initialContents.byteLength); + posInFileWrite += initialContents.byteLength; + + await fileProvider.read(fdRead, posInFileRead, buffer.buffer, 0, 26); + assert.equal(buffer.slice(0, 26).toString(), 'Lorem ipsum dolor sit amet'); + posInFileRead += 26; + + const contents = VSBuffer.fromString('Hello World'); + + await fileProvider.write(fdWrite, posInFileWrite, contents.buffer, 0, contents.byteLength); + posInFileWrite += contents.byteLength; + + await fileProvider.read(fdRead, posInFileRead, buffer.buffer, 0, contents.byteLength); + assert.equal(buffer.slice(0, contents.byteLength).toString(), 'Hello World'); + posInFileRead += contents.byteLength; + + await fileProvider.write(fdWrite, 6, contents.buffer, 0, contents.byteLength); + + await fileProvider.read(fdRead, 0, buffer.buffer, 0, 11); + assert.equal(buffer.slice(0, 11).toString(), 'Lorem Hello'); + + await fileProvider.write(fdWrite, posInFileWrite, contents.buffer, 0, contents.byteLength); + posInFileWrite += contents.byteLength; + + await fileProvider.read(fdRead, posInFileWrite - contents.byteLength, buffer.buffer, 0, contents.byteLength); + assert.equal(buffer.slice(0, contents.byteLength).toString(), 'Hello World'); + + await fileProvider.close(fdWrite); + await fileProvider.close(fdRead); + }); +}); diff --git a/src/vs/platform/history/electron-main/historyMainService.ts b/src/vs/platform/history/electron-main/historyMainService.ts index a462615fe4..db5e9dc50f 100644 --- a/src/vs/platform/history/electron-main/historyMainService.ts +++ b/src/vs/platform/history/electron-main/historyMainService.ts @@ -40,7 +40,7 @@ export class HistoryMainService implements IHistoryMainService { private static readonly recentlyOpenedStorageKey = 'openedPathsList'; - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private _onRecentlyOpenedChange = new Emitter(); readonly onRecentlyOpenedChange: CommonEvent = this._onRecentlyOpenedChange.event; @@ -387,4 +387,4 @@ function indexOfFolder(arr: IRecent[], folderURI: ISingleFolderWorkspaceIdentifi function indexOfFile(arr: IRecentFile[], fileURI: URI): number { return arrays.firstIndex(arr, f => areResourcesEqual(f.fileUri, fileURI)); -} \ No newline at end of file +} diff --git a/src/vs/platform/ipc/electron-browser/mainProcessService.ts b/src/vs/platform/ipc/electron-browser/mainProcessService.ts index 97d20f2e77..de03dae639 100644 --- a/src/vs/platform/ipc/electron-browser/mainProcessService.ts +++ b/src/vs/platform/ipc/electron-browser/mainProcessService.ts @@ -21,7 +21,7 @@ export interface IMainProcessService { export class MainProcessService extends Disposable implements IMainProcessService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private mainProcessConnection: Client; diff --git a/src/vs/platform/ipc/electron-browser/sharedProcessService.ts b/src/vs/platform/ipc/electron-browser/sharedProcessService.ts index dc3e99552e..f40fc2584e 100644 --- a/src/vs/platform/ipc/electron-browser/sharedProcessService.ts +++ b/src/vs/platform/ipc/electron-browser/sharedProcessService.ts @@ -23,7 +23,7 @@ export interface ISharedProcessService { export class SharedProcessService implements ISharedProcessService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private withSharedProcessConnection: Promise>; diff --git a/src/vs/platform/issue/electron-browser/issueService.ts b/src/vs/platform/issue/electron-browser/issueService.ts index a70412d99d..109ac4335d 100644 --- a/src/vs/platform/issue/electron-browser/issueService.ts +++ b/src/vs/platform/issue/electron-browser/issueService.ts @@ -10,7 +10,7 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export class IssueService implements IIssueService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private channel: IChannel; diff --git a/src/vs/platform/issue/electron-main/issueService.ts b/src/vs/platform/issue/electron-main/issueService.ts index 2ce6f999e6..c98bad3432 100644 --- a/src/vs/platform/issue/electron-main/issueService.ts +++ b/src/vs/platform/issue/electron-main/issueService.ts @@ -21,10 +21,10 @@ const DEFAULT_BACKGROUND_COLOR = '#1E1E1E'; export class IssueService implements IIssueService { _serviceBrand: any; - _issueWindow: BrowserWindow | null; - _issueParentWindow: BrowserWindow | null; - _processExplorerWindow: BrowserWindow | null; - _processExplorerParentWindow: BrowserWindow | null; + _issueWindow: BrowserWindow | null = null; + _issueParentWindow: BrowserWindow | null = null; + _processExplorerWindow: BrowserWindow | null = null; + _processExplorerParentWindow: BrowserWindow | null = null; constructor( private machineId: string, diff --git a/src/vs/platform/launch/electron-main/launchService.ts b/src/vs/platform/launch/electron-main/launchService.ts index 09ac10ac0d..3fdf0812be 100644 --- a/src/vs/platform/launch/electron-main/launchService.ts +++ b/src/vs/platform/launch/electron-main/launchService.ts @@ -94,7 +94,7 @@ export class LaunchChannel implements IServerChannel { export class LaunchChannelClient implements ILaunchService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; constructor(private channel: IChannel) { } @@ -121,7 +121,7 @@ export class LaunchChannelClient implements ILaunchService { export class LaunchService implements ILaunchService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; constructor( @ILogService private readonly logService: ILogService, diff --git a/src/vs/platform/lifecycle/browser/lifecycleService.ts b/src/vs/platform/lifecycle/browser/lifecycleService.ts index 24718edd82..1e3a593679 100644 --- a/src/vs/platform/lifecycle/browser/lifecycleService.ts +++ b/src/vs/platform/lifecycle/browser/lifecycleService.ts @@ -11,7 +11,7 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export class BrowserLifecycleService extends AbstractLifecycleService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; constructor( @ILogService readonly logService: ILogService @@ -56,4 +56,4 @@ export class BrowserLifecycleService extends AbstractLifecycleService { return null; } -} \ No newline at end of file +} diff --git a/src/vs/platform/lifecycle/common/lifecycleService.ts b/src/vs/platform/lifecycle/common/lifecycleService.ts index 4b0dadb746..70d3901da1 100644 --- a/src/vs/platform/lifecycle/common/lifecycleService.ts +++ b/src/vs/platform/lifecycle/common/lifecycleService.ts @@ -13,7 +13,7 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export abstract class AbstractLifecycleService extends Disposable implements ILifecycleService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; protected readonly _onBeforeShutdown = this._register(new Emitter()); readonly onBeforeShutdown: Event = this._onBeforeShutdown.event; diff --git a/src/vs/platform/lifecycle/electron-browser/lifecycleService.ts b/src/vs/platform/lifecycle/electron-browser/lifecycleService.ts index 6b66ce2b0f..22260de1df 100644 --- a/src/vs/platform/lifecycle/electron-browser/lifecycleService.ts +++ b/src/vs/platform/lifecycle/electron-browser/lifecycleService.ts @@ -18,7 +18,7 @@ export class LifecycleService extends AbstractLifecycleService { private static readonly LAST_SHUTDOWN_REASON_KEY = 'lifecyle.lastShutdownReason'; - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private shutdownReason: ShutdownReason; diff --git a/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts b/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts index 1cf48e6037..171409acf8 100644 --- a/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts +++ b/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts @@ -131,7 +131,7 @@ export const enum LifecycleMainPhase { export class LifecycleService extends Disposable implements ILifecycleService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private static readonly QUIT_FROM_RESTART_MARKER = 'quit.from.restart'; // use a marker to find out if the session was restarted diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index eeafec7b3b..84e59b5824 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -890,6 +890,7 @@ function workbenchTreeDataPreamble; + _serviceBrand!: ServiceIdentifier; private channel: IChannel; diff --git a/src/vs/platform/localizations/node/localizations.ts b/src/vs/platform/localizations/node/localizations.ts index 1cd5fed37a..609a286b9e 100644 --- a/src/vs/platform/localizations/node/localizations.ts +++ b/src/vs/platform/localizations/node/localizations.ts @@ -93,7 +93,7 @@ class LanguagePacksCache extends Disposable { private languagePacks: { [language: string]: ILanguagePack } = {}; private languagePacksFilePath: string; private languagePacksFileLimiter: Queue; - private initializedCache: boolean; + private initializedCache: boolean | undefined; constructor( @IEnvironmentService environmentService: IEnvironmentService, @@ -184,4 +184,4 @@ class LanguagePacksCache extends Disposable { .then(() => result, error => this.logService.error(error)); }); } -} \ No newline at end of file +} diff --git a/src/vs/platform/menubar/electron-browser/menubarService.ts b/src/vs/platform/menubar/electron-browser/menubarService.ts index 0743b3e213..7d6da91f4c 100644 --- a/src/vs/platform/menubar/electron-browser/menubarService.ts +++ b/src/vs/platform/menubar/electron-browser/menubarService.ts @@ -10,7 +10,7 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export class MenubarService implements IMenubarService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private channel: IChannel; diff --git a/src/vs/platform/notification/common/notification.ts b/src/vs/platform/notification/common/notification.ts index b982b85878..37624915fe 100644 --- a/src/vs/platform/notification/common/notification.ts +++ b/src/vs/platform/notification/common/notification.ts @@ -29,6 +29,26 @@ export interface INotificationProperties { * catch some attention. */ silent?: boolean; + + /** + * Adds an action to never show the notification again. The choice will be persisted + * such as future requests will not cause the notification to show again. + */ + neverShowAgain?: INeverShowAgainOptions; +} + +export interface INeverShowAgainOptions { + + /** + * The id is used to persist the selection of not showing the notification again. + */ + id: string; + + /** + * By default the action will show up as primary action. Setting this to true will + * make it a secondary action instead. + */ + isSecondary?: boolean; } export interface INotification extends INotificationProperties { @@ -270,4 +290,4 @@ export class NoOpProgress implements INotificationProgress { done(): void { } total(value: number): void { } worked(value: number): void { } -} \ No newline at end of file +} diff --git a/src/vs/platform/product/browser/productService.ts b/src/vs/platform/product/browser/productService.ts index ec4a2b76ee..ac49b82bf8 100644 --- a/src/vs/platform/product/browser/productService.ts +++ b/src/vs/platform/product/browser/productService.ts @@ -8,6 +8,8 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export class ProductService implements IProductService { + _serviceBrand!: ServiceIdentifier; + private readonly productConfiguration: IProductConfiguration | null; constructor() { @@ -15,9 +17,7 @@ export class ProductService implements IProductService { this.productConfiguration = element ? JSON.parse(element.getAttribute('data-settings')!) : null; } - _serviceBrand: ServiceIdentifier; - - get version(): string { return '1.35.0'; } + get version(): string { return this.productConfiguration && this.productConfiguration.version ? this.productConfiguration.version : '1.38.0-unknown'; } get vscodeVersion(): string { return '1.35.0'; } // {{SQL CARBON EDIT}} add vscodeversion @@ -25,7 +25,7 @@ export class ProductService implements IProductService { get commit(): string | undefined { return this.productConfiguration ? this.productConfiguration.commit : undefined; } - get nameLong(): string { return ''; } + get nameLong(): string { return this.productConfiguration ? this.productConfiguration.nameLong : 'Unknown'; } get urlProtocol(): string { return ''; } diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index ef3e0680cf..b2ead4e836 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -3,17 +3,19 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; export const IProductService = createDecorator('productService'); export interface IProductService { - _serviceBrand: any; + + _serviceBrand: ServiceIdentifier; readonly version: string; readonly vscodeVersion: string; // {{SQL CARBON EDIT}} add vscode version readonly recommendedExtensionsByScenario: { [area: string]: Array }; // {{SQL CARBON EDIT}} add getter readonly commit?: string; + readonly date?: string; readonly nameLong: string; readonly urlProtocol: string; @@ -46,6 +48,7 @@ export interface IProductService { } export interface IProductConfiguration { + readonly version: string; nameShort: string; nameLong: string; readonly applicationName: string; diff --git a/src/vs/platform/product/node/productService.ts b/src/vs/platform/product/node/productService.ts index 875cebf123..6e701ae2dd 100644 --- a/src/vs/platform/product/node/productService.ts +++ b/src/vs/platform/product/node/productService.ts @@ -10,7 +10,7 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export class ProductService implements IProductService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; get version(): string { return pkg.version; } diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index ba003b9b97..20625c64a6 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -90,13 +90,13 @@ export interface IProgress { export class Progress implements IProgress { private _callback: (data: T) => void; - private _value: T; + private _value?: T; constructor(callback: (data: T) => void) { this._callback = callback; } - get value() { + get value(): T | undefined { return this._value; } @@ -120,7 +120,7 @@ export interface IOperation { export class LongRunningOperation extends Disposable { private currentOperationId = 0; private readonly currentOperationDisposables = this._register(new DisposableStore()); - private currentProgressRunner: IProgressRunner; + private currentProgressRunner: IProgressRunner | undefined; private currentProgressTimeout: any; constructor( diff --git a/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts b/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts index 13b4e72122..c9b757d10e 100644 --- a/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts +++ b/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts @@ -33,7 +33,7 @@ export class RemoteExtensionsFileSystemProvider extends Disposable implements IF private readonly _onDidChangeCapabilities = this._register(new Emitter()); readonly onDidChangeCapabilities: Event = this._onDidChangeCapabilities.event; - private _capabilities: FileSystemProviderCapabilities; + private _capabilities!: FileSystemProviderCapabilities; get capabilities(): FileSystemProviderCapabilities { return this._capabilities; } constructor(private readonly channel: IChannel, environment: Promise) { diff --git a/src/vs/platform/request/node/requestService.ts b/src/vs/platform/request/node/requestService.ts index 31f444959d..84bb704151 100644 --- a/src/vs/platform/request/node/requestService.ts +++ b/src/vs/platform/request/node/requestService.ts @@ -38,7 +38,7 @@ export class RequestService extends Disposable implements IRequestService { _serviceBrand: any; private proxyUrl?: string; - private strictSSL: boolean; + private strictSSL: boolean | undefined; private authorization?: string; constructor( @@ -143,4 +143,4 @@ export class RequestService extends Disposable implements IRequestService { } -} \ No newline at end of file +} diff --git a/src/vs/platform/sign/browser/signService.ts b/src/vs/platform/sign/browser/signService.ts index 2ca9f40f0f..265a81f53b 100644 --- a/src/vs/platform/sign/browser/signService.ts +++ b/src/vs/platform/sign/browser/signService.ts @@ -8,7 +8,7 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export class SignService implements ISignService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private readonly _tkn: string | null; diff --git a/src/vs/platform/sign/node/signService.ts b/src/vs/platform/sign/node/signService.ts index 9fd50a8549..d273fe49b5 100644 --- a/src/vs/platform/sign/node/signService.ts +++ b/src/vs/platform/sign/node/signService.ts @@ -4,14 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { ISignService } from 'vs/platform/sign/common/sign'; -import { memoize } from 'vs/base/common/decorators'; export class SignService implements ISignService { _serviceBrand: any; - // Cache the 'vsda' import, because when the same missing module is imported multiple times, - // the ones after the first will not throw an error. And this will break the contract of the sign method. - @memoize private vsda(): Promise { return import('vsda'); } @@ -29,4 +25,4 @@ export class SignService implements ISignService { return value; } -} \ No newline at end of file +} diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts index 6081cdf8be..20801da3e5 100644 --- a/src/vs/platform/storage/browser/storageService.ts +++ b/src/vs/platform/storage/browser/storageService.ts @@ -17,7 +17,7 @@ import { runWhenIdle } from 'vs/base/common/async'; export class BrowserStorageService extends Disposable implements IStorageService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private readonly _onDidChangeStorage: Emitter = this._register(new Emitter()); readonly onDidChangeStorage: Event = this._onDidChangeStorage.event; @@ -28,11 +28,18 @@ export class BrowserStorageService extends Disposable implements IStorageService private globalStorage: IStorage; private workspaceStorage: IStorage; + private globalStorageDatabase: FileStorageDatabase; + private workspaceStorageDatabase: FileStorageDatabase; + private globalStorageFile: URI; private workspaceStorageFile: URI; private initializePromise: Promise; + get hasPendingUpdate(): boolean { + return this.globalStorageDatabase.hasPendingUpdate || this.workspaceStorageDatabase.hasPendingUpdate; + } + constructor( @IEnvironmentService private readonly environmentService: IEnvironmentService, @IFileService private readonly fileService: IFileService @@ -76,12 +83,14 @@ export class BrowserStorageService extends Disposable implements IStorageService // Workspace Storage this.workspaceStorageFile = joinPath(stateRoot, `${payload.id}.json`); - this.workspaceStorage = new Storage(this._register(new FileStorageDatabase(this.workspaceStorageFile, this.fileService))); + this.workspaceStorageDatabase = this._register(new FileStorageDatabase(this.workspaceStorageFile, this.fileService)); + this.workspaceStorage = new Storage(this.workspaceStorageDatabase); this._register(this.workspaceStorage.onDidChangeStorage(key => this._onDidChangeStorage.fire({ key, scope: StorageScope.WORKSPACE }))); // Global Storage this.globalStorageFile = joinPath(stateRoot, 'global.json'); - this.globalStorage = new Storage(this._register(new FileStorageDatabase(this.globalStorageFile, this.fileService))); + this.globalStorageDatabase = this._register(new FileStorageDatabase(this.globalStorageFile, this.fileService)); + this.globalStorage = new Storage(this.globalStorageDatabase); this._register(this.globalStorage.onDidChangeStorage(key => this._onDidChangeStorage.fire({ key, scope: StorageScope.GLOBAL }))); // Init both @@ -134,5 +143,9 @@ export class BrowserStorageService extends Disposable implements IStorageService // Signal as event so that clients can still store data this._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); + + // Close DBs + this.globalStorage.close(); + this.workspaceStorage.close(); } } diff --git a/src/vs/platform/storage/common/storage.ts b/src/vs/platform/storage/common/storage.ts index 468a6ccdf0..8691e4e242 100644 --- a/src/vs/platform/storage/common/storage.ts +++ b/src/vs/platform/storage/common/storage.ts @@ -220,6 +220,11 @@ export class FileStorageDatabase extends Disposable implements IStorageDatabase private pendingUpdate: Promise = Promise.resolve(); + private _hasPendingUpdate = false; + get hasPendingUpdate(): boolean { + return this._hasPendingUpdate; + } + constructor( private readonly file: URI, private readonly fileService: IFileService @@ -260,7 +265,13 @@ export class FileStorageDatabase extends Disposable implements IStorageDatabase await this.pendingUpdate; - this.pendingUpdate = this.fileService.writeFile(this.file, VSBuffer.fromString(JSON.stringify(mapToSerializable(items)))).then(); + this._hasPendingUpdate = true; + + this.pendingUpdate = this.fileService.writeFile(this.file, VSBuffer.fromString(JSON.stringify(mapToSerializable(items)))) + .then(() => undefined) + .finally(() => { + this._hasPendingUpdate = false; + }); return this.pendingUpdate; } @@ -312,4 +323,4 @@ export async function logStorage(global: Map, workspace: Map; + _serviceBrand!: ServiceIdentifier; private static STORAGE_NAME = 'state.vscdb'; diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts index 551b1c82b7..eb06ea0990 100644 --- a/src/vs/platform/storage/node/storageService.ts +++ b/src/vs/platform/storage/node/storageService.ts @@ -19,7 +19,7 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export class StorageService extends Disposable implements IStorageService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private static WORKSPACE_STORAGE_NAME = 'state.vscdb'; private static WORKSPACE_META_NAME = 'workspace.json'; @@ -81,7 +81,7 @@ export class StorageService extends Disposable implements IStorageService { const useInMemoryStorage = !!this.environmentService.extensionTestsLocationURI; // no storage during extension tests! - // Create workspace storage and initalize + // Create workspace storage and initialize mark('willInitWorkspaceStorage'); try { await this.createWorkspaceStorage(useInMemoryStorage ? SQLiteStorageDatabase.IN_MEMORY_PATH : join(result.path, StorageService.WORKSPACE_STORAGE_NAME), result.wasCreated ? StorageHint.STORAGE_DOES_NOT_EXIST : undefined).init(); diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 07b9fe0e86..8e9f171ef0 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -224,6 +224,10 @@ export const selectListBackground = registerColor('dropdown.listBackground', { d export const selectForeground = registerColor('dropdown.foreground', { dark: '#F0F0F0', light: null, hc: Color.white }, nls.localize('dropdownForeground', "Dropdown foreground.")); export const selectBorder = registerColor('dropdown.border', { dark: selectBackground, light: '#CECECE', hc: contrastBorder }, nls.localize('dropdownBorder', "Dropdown border.")); +export const simpleCheckboxBackground = registerColor('checkbox.background', { dark: selectBackground, light: selectBackground, hc: selectBackground }, nls.localize('checkbox.background', "Background color of checkbox widget.")); +export const simpleCheckboxForeground = registerColor('checkbox.foreground', { dark: selectForeground, light: selectForeground, hc: selectForeground }, nls.localize('checkbox.foreground', "Foreground color of checkbox widget.")); +export const simpleCheckboxBorder = registerColor('checkbox.border', { dark: selectBorder, light: selectBorder, hc: selectBorder }, nls.localize('checkbox.border', "Border color of checkbox widget.")); + export const listFocusBackground = registerColor('list.focusBackground', { dark: '#062F4A', light: '#D6EBFF', hc: null }, nls.localize('listFocusBackground', "List/Tree background color for the focused item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); export const listFocusForeground = registerColor('list.focusForeground', { dark: null, light: null, hc: null }, nls.localize('listFocusForeground', "List/Tree foreground color for the focused item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); export const listActiveSelectionBackground = registerColor('list.activeSelectionBackground', { dark: '#094771', light: '#0074E8', hc: null }, nls.localize('listActiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index 94182b9aba..cb20dad3ff 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; -import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, badgeBackground, badgeForeground, progressBarBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, editorWidgetBorder, inputValidationInfoForeground, inputValidationWarningForeground, inputValidationErrorForeground, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuBorder, menuSeparatorBackground, darken, listFilterWidgetOutline, listFilterWidgetNoMatchesOutline, listFilterWidgetBackground, editorWidgetBackground, treeIndentGuidesStroke, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry'; +import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, badgeBackground, badgeForeground, progressBarBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, editorWidgetBorder, inputValidationInfoForeground, inputValidationWarningForeground, inputValidationErrorForeground, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuBorder, menuSeparatorBackground, darken, listFilterWidgetOutline, listFilterWidgetNoMatchesOutline, listFilterWidgetBackground, editorWidgetBackground, treeIndentGuidesStroke, editorWidgetForeground, simpleCheckboxBackground, simpleCheckboxBorder, simpleCheckboxForeground } from 'vs/platform/theme/common/colorRegistry'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; @@ -341,6 +341,9 @@ export interface IDialogStyleOverrides extends IButtonStyleOverrides { dialogBackground?: ColorIdentifier; dialogShadow?: ColorIdentifier; dialogBorder?: ColorIdentifier; + checkboxBorder?: ColorIdentifier; + checkboxBackground?: ColorIdentifier; + checkboxForeground?: ColorIdentifier; } export const defaultDialogStyles = { @@ -351,7 +354,10 @@ export const defaultDialogStyles = { buttonForeground: buttonForeground, buttonBackground: buttonBackground, buttonHoverBackground: buttonHoverBackground, - buttonBorder: contrastBorder + buttonBorder: contrastBorder, + checkboxBorder: simpleCheckboxBorder, + checkboxBackground: simpleCheckboxBackground, + checkboxForeground: simpleCheckboxForeground }; diff --git a/src/vs/platform/update/electron-browser/updateService.ts b/src/vs/platform/update/electron-browser/updateService.ts index 30fb9adc8c..06502cc228 100644 --- a/src/vs/platform/update/electron-browser/updateService.ts +++ b/src/vs/platform/update/electron-browser/updateService.ts @@ -11,7 +11,7 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export class UpdateService implements IUpdateService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private _onStateChange = new Emitter(); readonly onStateChange: Event = this._onStateChange.event; diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index 6d95d15318..ff648d5823 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -81,8 +81,15 @@ export abstract class AbstractUpdateService implements IUpdateService { return; } - // Start checking for updates after 30 seconds - this.scheduleCheckForUpdates(30 * 1000).then(undefined, err => this.logService.error(err)); + if (updateMode === 'start') { + this.logService.info('update#ctor - startup checks only; automatic updates are disabled by user preference'); + + // Check for updates only once after 30 seconds + setTimeout(() => this.checkForUpdates(null), 30 * 1000); + } else { + // Start checking for updates after 30 seconds + this.scheduleCheckForUpdates(30 * 1000).then(undefined, err => this.logService.error(err)); + } } private getProductQuality(updateMode: string): string | undefined { diff --git a/src/vs/platform/update/node/update.config.contribution.ts b/src/vs/platform/update/node/update.config.contribution.ts index 1003e7a210..ebbf16f84c 100644 --- a/src/vs/platform/update/node/update.config.contribution.ts +++ b/src/vs/platform/update/node/update.config.contribution.ts @@ -17,7 +17,7 @@ configurationRegistry.registerConfiguration({ properties: { 'update.mode': { type: 'string', - enum: ['none', 'manual', 'default'], + enum: ['none', 'manual', 'start', 'default'], default: 'default', scope: ConfigurationScope.APPLICATION, description: localize('updateMode', "Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service."), @@ -25,6 +25,7 @@ configurationRegistry.registerConfiguration({ enumDescriptions: [ localize('none', "Disable updates."), localize('manual', "Disable automatic background update checks. Updates will be available if you manually check for updates."), + localize('start', "Check for updates only on startup. Disable automatic background update checks."), localize('default', "Enable automatic update checks. Code will check for updates automatically and periodically.") ] }, diff --git a/src/vs/platform/url/common/urlService.ts b/src/vs/platform/url/common/urlService.ts index 39c8cff654..a6eb375558 100644 --- a/src/vs/platform/url/common/urlService.ts +++ b/src/vs/platform/url/common/urlService.ts @@ -12,7 +12,7 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export class URLService implements IURLService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private handlers = new Set(); diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 29c6aed88a..2420981064 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -303,7 +303,7 @@ export function getTitleBarStyle(configurationService: IConfigurationService, en return 'native'; // native tabs on sierra do not work with custom title style } - const useSimpleFullScreen = false; //isMacintosh && configuration.nativeFullScreen === false; + const useSimpleFullScreen = isMacintosh && configuration.nativeFullScreen === false; if (useSimpleFullScreen) { return 'native'; // simple fullscreen does not work well with custom title style (https://github.com/Microsoft/vscode/issues/63291) } @@ -426,6 +426,7 @@ export interface IWindowConfiguration extends ParsedArgs { folderUri?: ISingleFolderWorkspaceIdentifier; remoteAuthority?: string; + connectionToken?: string; zoomLevel?: number; fullscreen?: boolean; @@ -444,7 +445,6 @@ export interface IWindowConfiguration extends ParsedArgs { filesToDiff?: IPath[]; filesToWait?: IPathsToWaitFor; termProgram?: string; - connectionToken?: string; } export interface IRunActionInWindowRequest { diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index 975630cd4f..e573543f0c 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -27,7 +27,7 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export class WindowsService extends Disposable implements IWindowsService, IURLHandler { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private readonly disposables = this._register(new DisposableStore()); diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 0ad45e6738..126fed8622 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -2786,7 +2786,7 @@ declare module 'vscode' { * *Note* that the eol-sequence will be applied to the * whole document. */ - newEol: EndOfLine; + newEol?: EndOfLine; /** * Create a new TextEdit. @@ -8092,9 +8092,14 @@ declare module 'vscode' { * result. A failing provider (rejected promise or exception) will not fail the whole * operation. * + * A completion item provider can be associated with a set of `triggerCharacters`. When trigger + * characters are being typed, completions are requested but only from providers that registered + * the typed character. Because of that trigger characters should be different than [word characters](#LanguageConfiguration.wordPattern), + * a common trigger character is `.` to trigger member completions. + * * @param selector A selector that defines the documents this provider is applicable to. * @param provider A completion provider. - * @param triggerCharacters Trigger completion when the user types one of the characters, like `.` or `:`. + * @param triggerCharacters Trigger completion when the user types one of the characters. * @return A [disposable](#Disposable) that unregisters this provider when being disposed. */ export function registerCompletionItemProvider(selector: DocumentSelector, provider: CompletionItemProvider, ...triggerCharacters: string[]): Disposable; diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 33d3d95813..35796b7251 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -752,11 +752,29 @@ declare module 'vscode' { readonly dimensions: TerminalDimensions; } + export interface TerminalDataWriteEvent { + /** + * The [terminal](#Terminal) for which the data was written. + */ + readonly terminal: Terminal; + /** + * The data being written. + */ + readonly data: string; + } + namespace window { /** * An event which fires when the [dimensions](#Terminal.dimensions) of the terminal change. */ export const onDidChangeTerminalDimensions: Event; + + /** + * An event which fires when the terminal's pty slave pseudo-device is written to. In other + * words, this provides access to the raw data stream from the process running within the + * terminal, including VT sequences. + */ + export const onDidWriteTerminalData: Event; } export interface Terminal { @@ -771,22 +789,12 @@ declare module 'vscode' { * Fires when the terminal's pty slave pseudo-device is written to. In other words, this * provides access to the raw data stream from the process running within the terminal, * including VT sequences. + * + * @deprecated Use [window.onDidWriteTerminalData](#onDidWriteTerminalData). */ readonly onDidWriteData: Event; } - - export interface TerminalOptions { - /** - * When enabled the terminal will run the process as normal but not be surfaced to the user - * until `Terminal.show` is called. The typical usage for this is when you need to run - * something that may need interactivity but only want to tell the user about it when - * interaction is needed. Note that the terminals will still be exposed to all extensions - * as normal. - */ - runInBackground?: boolean; - } - /** * Represents the dimensions of a terminal. */ @@ -802,134 +810,13 @@ declare module 'vscode' { readonly rows: number; } - /** - * Represents a terminal without a process where all interaction and output in the terminal is - * controlled by an extension. This is similar to an output window but has the same VT sequence - * compatibility as the regular terminal. - * - * Note that an instance of [Terminal](#Terminal) will be created when a TerminalRenderer is - * created with all its APIs available for use by extensions. When using the Terminal object - * of a TerminalRenderer it acts just like normal only the extension that created the - * TerminalRenderer essentially acts as a process. For example when an - * [Terminal.onDidWriteData](#Terminal.onDidWriteData) listener is registered, that will fire - * when [TerminalRenderer.write](#TerminalRenderer.write) is called. Similarly when - * [Terminal.sendText](#Terminal.sendText) is triggered that will fire the - * [TerminalRenderer.onDidAcceptInput](#TerminalRenderer.onDidAcceptInput) event. - * - * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. - * - * **Example:** Create a terminal renderer, show it and write hello world in red - * ```typescript - * const renderer = window.createTerminalRenderer('foo'); - * renderer.terminal.then(t => t.show()); - * renderer.write('\x1b[31mHello world\x1b[0m'); - * ``` - */ - export interface TerminalRenderer { - /** - * The name of the terminal, this will appear in the terminal selector. - * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. - */ - name: string; - - /** - * The dimensions of the terminal, the rows and columns of the terminal can only be set to - * a value smaller than the maximum value, if this is undefined the terminal will auto fit - * to the maximum value [maximumDimensions](TerminalRenderer.maximumDimensions). - * - * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. - * - * **Example:** Override the dimensions of a TerminalRenderer to 20 columns and 10 rows - * ```typescript - * terminalRenderer.dimensions = { - * cols: 20, - * rows: 10 - * }; - * ``` - */ - dimensions: TerminalDimensions | undefined; - - /** - * The maximum dimensions of the terminal, this will be undefined immediately after a - * terminal renderer is created and also until the terminal becomes visible in the UI. - * Listen to [onDidChangeMaximumDimensions](TerminalRenderer.onDidChangeMaximumDimensions) - * to get notified when this value changes. - * - * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. - */ - readonly maximumDimensions: TerminalDimensions | undefined; - - /** - * The corresponding [Terminal](#Terminal) for this TerminalRenderer. - * - * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. - */ - readonly terminal: Terminal; - - /** - * Write text to the terminal. Unlike [Terminal.sendText](#Terminal.sendText) which sends - * text to the underlying _process_, this will write the text to the terminal itself. - * - * @param text The text to write. - * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. - * - * **Example:** Write red text to the terminal - * ```typescript - * terminalRenderer.write('\x1b[31mHello world\x1b[0m'); - * ``` - * - * **Example:** Move the cursor to the 10th row and 20th column and write an asterisk - * ```typescript - * terminalRenderer.write('\x1b[10;20H*'); - * ``` - */ - write(text: string): void; - - /** - * An event which fires on keystrokes in the terminal or when an extension calls - * [Terminal.sendText](#Terminal.sendText). Keystrokes are converted into their - * corresponding VT sequence representation. - * - * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. - * - * **Example:** Simulate interaction with the terminal from an outside extension or a - * workbench command such as `workbench.action.terminal.runSelectedText` - * ```typescript - * const terminalRenderer = window.createTerminalRenderer('test'); - * terminalRenderer.onDidAcceptInput(data => { - * console.log(data); // 'Hello world' - * }); - * terminalRenderer.terminal.sendText('Hello world'); - * ``` - */ - readonly onDidAcceptInput: Event; - - /** - * An event which fires when the [maximum dimensions](#TerminalRenderer.maximumDimensions) of - * the terminal renderer change. - * - * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. - */ - readonly onDidChangeMaximumDimensions: Event; - } - - export namespace window { - /** - * Create a [TerminalRenderer](#TerminalRenderer). - * - * @param name The name of the terminal renderer, this shows up in the terminal selector. - * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. - */ - export function createTerminalRenderer(name: string): TerminalRenderer; - } - //#endregion //#region Extension terminals export namespace window { /** - * Creates a [Terminal](#Terminal) where an extension controls the teerminal. + * Creates a [Terminal](#Terminal) where an extension controls the terminal. * * @param options An [ExtensionTerminalOptions](#ExtensionTerminalOptions) object describing * the characteristics of the new terminal. @@ -1125,7 +1012,7 @@ declare module 'vscode' { /** * An optional human-readable message that will be rendered in the view. */ - message?: string | MarkdownString; + message?: string; } @@ -1162,30 +1049,12 @@ declare module 'vscode' { //#endregion //#region CustomExecution - /** - * Class used to execute an extension callback as a task. - */ - export class CustomExecution { - /** - * @param callback The callback that will be called when the extension callback task is executed. - */ - constructor(callback: (terminalRenderer: TerminalRenderer, cancellationToken: CancellationToken, thisArg?: any) => Thenable); - - /** - * The callback used to execute the task. - * @param terminalRenderer Used by the task to render output and receive input. - * @param cancellationToken Cancellation used to signal a cancel request to the executing task. - * @returns The callback should return '0' for success and a non-zero value for failure. - */ - callback: (terminalRenderer: TerminalRenderer, cancellationToken: CancellationToken, thisArg?: any) => Thenable; - } - /** * Class used to execute an extension callback as a task. */ export class CustomExecution2 { /** - * @param process The [Pseudotrminal](#Pseudoterminal) to be used by the task to display output. + * @param process The [Pseudoterminal](#Pseudoterminal) to be used by the task to display output. * @param callback The callback that will be called when the task is started by a user. */ constructor(callback: (thisArg?: any) => Thenable); @@ -1214,12 +1083,12 @@ declare module 'vscode' { * or '$eslint'. Problem matchers can be contributed by an extension using * the `problemMatchers` extension point. */ - constructor(taskDefinition: TaskDefinition, scope: WorkspaceFolder | TaskScope.Global | TaskScope.Workspace, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution | CustomExecution2, problemMatchers?: string | string[]); + constructor(taskDefinition: TaskDefinition, scope: WorkspaceFolder | TaskScope.Global | TaskScope.Workspace, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution2, problemMatchers?: string | string[]); /** * The task's execution engine */ - execution2?: ProcessExecution | ShellExecution | CustomExecution | CustomExecution2; + execution2?: ProcessExecution | ShellExecution | CustomExecution2; } //#endregion diff --git a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts index f426840991..b8e38f8f8a 100644 --- a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts +++ b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts @@ -21,7 +21,7 @@ class EditorWebviewZone implements IViewZone { readonly afterColumn: number; readonly heightInLines: number; - private _id: number; + private _id?: number; // suppressMouseDown?: boolean | undefined; // heightInPx?: number | undefined; // minWidthInPx?: number | undefined; @@ -46,7 +46,7 @@ class EditorWebviewZone implements IViewZone { } dispose(): void { - this.editor.changeViewZones(accessor => accessor.removeZone(this._id)); + this.editor.changeViewZones(accessor => this._id && accessor.removeZone(this._id)); } } diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index e9412e3105..543b72d70c 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -16,9 +16,10 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { Extensions as PanelExtensions, PanelDescriptor, PanelRegistry } from 'vs/workbench/browser/panel'; import { ICommentInfo, ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; -import { CommentsPanel, COMMENTS_PANEL_ID, COMMENTS_PANEL_TITLE } from 'vs/workbench/contrib/comments/browser/commentsPanel'; +import { CommentsPanel } from 'vs/workbench/contrib/comments/browser/commentsPanel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { CommentProviderFeatures, ExtHostCommentsShape, ExtHostContext, IExtHostContext, MainContext, MainThreadCommentsShape } from '../common/extHost.protocol'; +import { COMMENTS_PANEL_ID, COMMENTS_PANEL_TITLE } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; export class MainThreadCommentThread implements modes.CommentThread { @@ -35,13 +36,13 @@ export class MainThreadCommentThread implements modes.CommentThread { private _onDidChangeInput = new Emitter(); get onDidChangeInput(): Event { return this._onDidChangeInput.event; } - private _label: string; + private _label: string | undefined; - get label(): string { + get label(): string | undefined { return this._label; } - set label(label: string) { + set label(label: string | undefined) { this._label = label; this._onDidChangeLabel.fire(this._label); } @@ -56,8 +57,8 @@ export class MainThreadCommentThread implements modes.CommentThread { this._contextValue = context; } - private _onDidChangeLabel = new Emitter(); - readonly onDidChangeLabel: Event = this._onDidChangeLabel.event; + private _onDidChangeLabel = new Emitter(); + readonly onDidChangeLabel: Event = this._onDidChangeLabel.event; private _comments: modes.Comment[] | undefined; @@ -348,7 +349,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments private _activeCommentThread?: MainThreadCommentThread; private readonly _activeCommentThreadDisposables = this._register(new DisposableStore()); - private _openPanelListener: IDisposable | null; + private _openPanelListener: IDisposable | null = null; constructor( diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index c00cecd7a9..fecbe4df62 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -153,13 +153,13 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb type: debugType }; if (hasProvide) { - provider.provideDebugConfigurations = (folder) => { - return this._proxy.$provideDebugConfigurations(handle, folder); + provider.provideDebugConfigurations = (folder, token) => { + return this._proxy.$provideDebugConfigurations(handle, folder, token); }; } if (hasResolve) { - provider.resolveDebugConfiguration = (folder, config) => { - return this._proxy.$resolveDebugConfiguration(handle, folder, config); + provider.resolveDebugConfiguration = (folder, config, token) => { + return this._proxy.$resolveDebugConfiguration(handle, folder, config, token); }; } if (hasProvideDebugAdapter) { diff --git a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts index f7fd9765b5..a385805478 100644 --- a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts @@ -109,7 +109,7 @@ class DocumentAndEditorStateDelta { class DocumentAndEditorState { - static compute(before: DocumentAndEditorState, after: DocumentAndEditorState): DocumentAndEditorStateDelta { + static compute(before: DocumentAndEditorState | undefined, after: DocumentAndEditorState): DocumentAndEditorStateDelta { if (!before) { return new DocumentAndEditorStateDelta( [], values(after.documents), @@ -146,7 +146,7 @@ class MainThreadDocumentAndEditorStateComputer { private readonly _toDispose = new DisposableStore(); private _toDisposeOnEditorRemove = new Map(); - private _currentState: DocumentAndEditorState; + private _currentState?: DocumentAndEditorState; private _activeEditorOrder: ActiveEditorOrder = ActiveEditorOrder.Editor; constructor( diff --git a/src/vs/workbench/api/browser/mainThreadEditor.ts b/src/vs/workbench/api/browser/mainThreadEditor.ts index 724aa481ad..b1411d03aa 100644 --- a/src/vs/workbench/api/browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/browser/mainThreadEditor.ts @@ -178,7 +178,7 @@ export class MainThreadTextEditor { private readonly _focusTracker: IFocusTracker; private readonly _codeEditorListeners = new DisposableStore(); - private _properties: MainThreadTextEditorProperties; + private _properties: MainThreadTextEditorProperties | null; private readonly _onPropertiesChanged: Emitter; constructor( @@ -191,6 +191,7 @@ export class MainThreadTextEditor { this._id = id; this._model = model; this._codeEditor = null; + this._properties = null; this._focusTracker = focusTracker; this._modelService = modelService; @@ -289,7 +290,7 @@ export class MainThreadTextEditor { } public getProperties(): MainThreadTextEditorProperties { - return this._properties; + return this._properties!; } public get onPropertiesChanged(): Event { @@ -304,7 +305,7 @@ export class MainThreadTextEditor { const newSelections = selections.map(Selection.liftSelection); this._setProperties( - new MainThreadTextEditorProperties(newSelections, this._properties.options, this._properties.visibleRanges), + new MainThreadTextEditorProperties(newSelections, this._properties!.options, this._properties!.visibleRanges), null ); } diff --git a/src/vs/workbench/api/browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts index 5f5427ab06..7bd901a489 100644 --- a/src/vs/workbench/api/browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -20,7 +20,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { IOpenerService } from 'vs/platform/opener/common/opener'; import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/browser/mainThreadDocumentsAndEditors'; import { MainThreadTextEditor } from 'vs/workbench/api/browser/mainThreadEditor'; -import { ExtHostContext, ExtHostEditorsShape, IApplyEditsOptions, IExtHostContext, ITextDocumentShowOptions, ITextEditorConfigurationUpdate, ITextEditorPositionData, IUndoStopOptions, MainThreadTextEditorsShape, TextEditorRevealType, WorkspaceEditDto, reviveWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostContext, ExtHostEditorsShape, IApplyEditsOptions, IExtHostContext, ITextDocumentShowOptions, ITextEditorConfigurationUpdate, ITextEditorPositionData, IUndoStopOptions, MainThreadTextEditorsShape, TextEditorRevealType, IWorkspaceEditDto, reviveWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { EditorViewColumn, editorGroupToViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -212,7 +212,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { return Promise.resolve(editor.applyEdits(modelVersionId, edits, opts)); } - $tryApplyWorkspaceEdit(dto: WorkspaceEditDto): Promise { + $tryApplyWorkspaceEdit(dto: IWorkspaceEditDto): Promise { const { edits } = reviveWorkspaceEditDto(dto); return this._bulkEditService.apply({ edits }, undefined).then(() => true, err => false); } diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 13a9670cb5..382acc6b55 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -11,7 +11,7 @@ import * as search from 'vs/workbench/contrib/search/common/search'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Position as EditorPosition } from 'vs/editor/common/core/position'; import { Range as EditorRange, IRange } from 'vs/editor/common/core/range'; -import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, WorkspaceSymbolDto, reviveWorkspaceEditDto, ISerializedDocumentFilter, DefinitionLinkDto, ISerializedSignatureHelpProviderMetadata, LinkDto, CallHierarchyDto, SuggestDataDto, CodeActionDto } from '../common/extHost.protocol'; +import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, IDefinitionLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyDto, ISuggestDataDto, ICodeActionDto } from '../common/extHost.protocol'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -54,9 +54,9 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha //#region --- revive functions - private static _reviveLocationDto(data: LocationDto): modes.Location; - private static _reviveLocationDto(data: LocationDto[]): modes.Location[]; - private static _reviveLocationDto(data: LocationDto | LocationDto[]): modes.Location | modes.Location[] { + private static _reviveLocationDto(data: ILocationDto): modes.Location; + private static _reviveLocationDto(data: ILocationDto[]): modes.Location[]; + private static _reviveLocationDto(data: ILocationDto | ILocationDto[]): modes.Location | modes.Location[] { if (!data) { return data; } else if (Array.isArray(data)) { @@ -68,9 +68,9 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } } - private static _reviveLocationLinkDto(data: DefinitionLinkDto): modes.LocationLink; - private static _reviveLocationLinkDto(data: DefinitionLinkDto[]): modes.LocationLink[]; - private static _reviveLocationLinkDto(data: DefinitionLinkDto | DefinitionLinkDto[]): modes.LocationLink | modes.LocationLink[] { + private static _reviveLocationLinkDto(data: IDefinitionLinkDto): modes.LocationLink; + private static _reviveLocationLinkDto(data: IDefinitionLinkDto[]): modes.LocationLink[]; + private static _reviveLocationLinkDto(data: IDefinitionLinkDto | IDefinitionLinkDto[]): modes.LocationLink | modes.LocationLink[] { if (!data) { return data; } else if (Array.isArray(data)) { @@ -82,10 +82,10 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } } - private static _reviveWorkspaceSymbolDto(data: WorkspaceSymbolDto): search.IWorkspaceSymbol; - private static _reviveWorkspaceSymbolDto(data: WorkspaceSymbolDto[]): search.IWorkspaceSymbol[]; + private static _reviveWorkspaceSymbolDto(data: IWorkspaceSymbolDto): search.IWorkspaceSymbol; + private static _reviveWorkspaceSymbolDto(data: IWorkspaceSymbolDto[]): search.IWorkspaceSymbol[]; private static _reviveWorkspaceSymbolDto(data: undefined): undefined; - private static _reviveWorkspaceSymbolDto(data: WorkspaceSymbolDto | WorkspaceSymbolDto[] | undefined): search.IWorkspaceSymbol | search.IWorkspaceSymbol[] | undefined { + private static _reviveWorkspaceSymbolDto(data: IWorkspaceSymbolDto | IWorkspaceSymbolDto[] | undefined): search.IWorkspaceSymbol | search.IWorkspaceSymbol[] | undefined { if (!data) { return data; } else if (Array.isArray(data)) { @@ -97,21 +97,21 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } } - private static _reviveCodeActionDto(data: ReadonlyArray): modes.CodeAction[] { + private static _reviveCodeActionDto(data: ReadonlyArray): modes.CodeAction[] { if (data) { data.forEach(code => reviveWorkspaceEditDto(code.edit)); } return data; } - private static _reviveLinkDTO(data: LinkDto): modes.ILink { + private static _reviveLinkDTO(data: ILinkDto): modes.ILink { if (data.url && typeof data.url !== 'string') { data.url = URI.revive(data.url); } return data; } - private static _reviveCallHierarchyItemDto(data: CallHierarchyDto | undefined): callh.CallHierarchyItem { + private static _reviveCallHierarchyItemDto(data: ICallHierarchyDto | undefined): callh.CallHierarchyItem { if (data) { data.uri = URI.revive(data.uri); } @@ -122,7 +122,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- outline - $registerDocumentSymbolProvider(handle: number, selector: ISerializedDocumentFilter[], displayName: string): void { + $registerDocumentSymbolProvider(handle: number, selector: IDocumentFilterDto[], displayName: string): void { this._registrations.set(handle, modes.DocumentSymbolProviderRegistry.register(selector, { displayName, provideDocumentSymbols: (model: ITextModel, token: CancellationToken): Promise => { @@ -133,7 +133,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- code lens - $registerCodeLensSupport(handle: number, selector: ISerializedDocumentFilter[], eventHandle: number | undefined): void { + $registerCodeLensSupport(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void { const provider = { provideCodeLenses: (model: ITextModel, token: CancellationToken): Promise => { @@ -170,7 +170,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- declaration - $registerDefinitionSupport(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerDefinitionSupport(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, modes.DefinitionProviderRegistry.register(selector, { provideDefinition: (model, position, token): Promise => { return this._proxy.$provideDefinition(handle, model.uri, position, token).then(MainThreadLanguageFeatures._reviveLocationLinkDto); @@ -178,7 +178,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha })); } - $registerDeclarationSupport(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerDeclarationSupport(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, modes.DeclarationProviderRegistry.register(selector, { provideDeclaration: (model, position, token) => { return this._proxy.$provideDeclaration(handle, model.uri, position, token).then(MainThreadLanguageFeatures._reviveLocationLinkDto); @@ -186,7 +186,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha })); } - $registerImplementationSupport(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerImplementationSupport(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, modes.ImplementationProviderRegistry.register(selector, { provideImplementation: (model, position, token): Promise => { return this._proxy.$provideImplementation(handle, model.uri, position, token).then(MainThreadLanguageFeatures._reviveLocationLinkDto); @@ -194,7 +194,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha })); } - $registerTypeDefinitionSupport(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerTypeDefinitionSupport(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, modes.TypeDefinitionProviderRegistry.register(selector, { provideTypeDefinition: (model, position, token): Promise => { return this._proxy.$provideTypeDefinition(handle, model.uri, position, token).then(MainThreadLanguageFeatures._reviveLocationLinkDto); @@ -204,7 +204,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- extra info - $registerHoverProvider(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerHoverProvider(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, modes.HoverProviderRegistry.register(selector, { provideHover: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise => { return this._proxy.$provideHover(handle, model.uri, position, token); @@ -214,7 +214,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- occurrences - $registerDocumentHighlightProvider(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, modes.DocumentHighlightProviderRegistry.register(selector, { provideDocumentHighlights: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise => { return this._proxy.$provideDocumentHighlights(handle, model.uri, position, token); @@ -224,7 +224,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- references - $registerReferenceSupport(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, modes.ReferenceProviderRegistry.register(selector, { provideReferences: (model: ITextModel, position: EditorPosition, context: modes.ReferenceContext, token: CancellationToken): Promise => { return this._proxy.$provideReferences(handle, model.uri, position, context, token).then(MainThreadLanguageFeatures._reviveLocationDto); @@ -234,7 +234,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- quick fix - $registerQuickFixSupport(handle: number, selector: ISerializedDocumentFilter[], providedCodeActionKinds?: string[]): void { + $registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], providedCodeActionKinds?: string[]): void { this._registrations.set(handle, modes.CodeActionProviderRegistry.register(selector, { provideCodeActions: async (model: ITextModel, rangeOrSelection: EditorRange | Selection, context: modes.CodeActionContext, token: CancellationToken): Promise => { const listDto = await this._proxy.$provideCodeActions(handle, model.uri, rangeOrSelection, context, token); @@ -256,7 +256,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- formatting - $registerDocumentFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], extensionId: ExtensionIdentifier, displayName: string): void { + $registerDocumentFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void { this._registrations.set(handle, modes.DocumentFormattingEditProviderRegistry.register(selector, { extensionId, displayName, @@ -266,7 +266,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha })); } - $registerRangeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], extensionId: ExtensionIdentifier, displayName: string): void { + $registerRangeFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void { this._registrations.set(handle, modes.DocumentRangeFormattingEditProviderRegistry.register(selector, { extensionId, displayName, @@ -276,7 +276,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha })); } - $registerOnTypeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void { + $registerOnTypeFormattingSupport(handle: number, selector: IDocumentFilterDto[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void { this._registrations.set(handle, modes.OnTypeFormattingEditProviderRegistry.register(selector, { extensionId, autoFormatTriggerCharacters, @@ -313,7 +313,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- rename - $registerRenameSupport(handle: number, selector: ISerializedDocumentFilter[], supportResolveLocation: boolean): void { + $registerRenameSupport(handle: number, selector: IDocumentFilterDto[], supportResolveLocation: boolean): void { this._registrations.set(handle, modes.RenameProviderRegistry.register(selector, { provideRenameEdits: (model: ITextModel, position: EditorPosition, newName: string, token: CancellationToken): Promise => { return this._proxy.$provideRenameEdits(handle, model.uri, position, newName, token).then(reviveWorkspaceEditDto); @@ -326,7 +326,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- suggest - private static _inflateSuggestDto(defaultRange: IRange, data: SuggestDataDto): modes.CompletionItem { + private static _inflateSuggestDto(defaultRange: IRange, data: ISuggestDataDto): modes.CompletionItem { return { label: data.a, kind: data.b, @@ -346,7 +346,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha }; } - $registerSuggestSupport(handle: number, selector: ISerializedDocumentFilter[], triggerCharacters: string[], supportsResolveDetails: boolean, extensionId: ExtensionIdentifier): void { + $registerSuggestSupport(handle: number, selector: IDocumentFilterDto[], triggerCharacters: string[], supportsResolveDetails: boolean, extensionId: ExtensionIdentifier): void { const provider: modes.CompletionItemProvider = { triggerCharacters, _debugDisplayName: extensionId.value, @@ -380,7 +380,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- parameter hints - $registerSignatureHelpProvider(handle: number, selector: ISerializedDocumentFilter[], metadata: ISerializedSignatureHelpProviderMetadata): void { + $registerSignatureHelpProvider(handle: number, selector: IDocumentFilterDto[], metadata: ISignatureHelpProviderMetadataDto): void { this._registrations.set(handle, modes.SignatureHelpProviderRegistry.register(selector, { signatureHelpTriggerCharacters: metadata.triggerCharacters, @@ -403,7 +403,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- links - $registerDocumentLinkProvider(handle: number, selector: ISerializedDocumentFilter[], supportsResolve: boolean): void { + $registerDocumentLinkProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean): void { const provider: modes.LinkProvider = { provideLinks: (model, token) => { return this._proxy.$provideDocumentLinks(handle, model.uri, token).then(dto => { @@ -423,7 +423,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha }; if (supportsResolve) { provider.resolveLink = (link, token) => { - const dto: LinkDto = link; + const dto: ILinkDto = link; if (!dto.cacheId) { return link; } @@ -437,7 +437,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- colors - $registerDocumentColorProvider(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerDocumentColorProvider(handle: number, selector: IDocumentFilterDto[]): void { const proxy = this._proxy; this._registrations.set(handle, modes.ColorProviderRegistry.register(selector, { provideDocumentColors: (model, token) => { @@ -471,7 +471,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- folding - $registerFoldingRangeProvider(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerFoldingRangeProvider(handle: number, selector: IDocumentFilterDto[]): void { const proxy = this._proxy; this._registrations.set(handle, modes.FoldingRangeProviderRegistry.register(selector, { provideFoldingRanges: (model, context, token) => { @@ -482,7 +482,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // -- smart select - $registerSelectionRangeProvider(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerSelectionRangeProvider(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, modes.SelectionRangeRegistry.register(selector, { provideSelectionRanges: (model, positions, token) => { return this._proxy.$provideSelectionRanges(handle, model.uri, positions, token); @@ -492,7 +492,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- call hierarchy - $registerCallHierarchyProvider(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerCallHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, callh.CallHierarchyProviderRegistry.register(selector, { provideCallHierarchyItem: (document, position, token) => { return this._proxy.$provideCallHierarchyItem(handle, document.uri, position, token).then(MainThreadLanguageFeatures._reviveCallHierarchyItemDto); @@ -516,11 +516,11 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- configuration - private static _reviveRegExp(regExp: ISerializedRegExp): RegExp { + private static _reviveRegExp(regExp: IRegExpDto): RegExp { return new RegExp(regExp.pattern, regExp.flags); } - private static _reviveIndentationRule(indentationRule: ISerializedIndentationRule): IndentationRule { + private static _reviveIndentationRule(indentationRule: IIndentationRuleDto): IndentationRule { return { decreaseIndentPattern: MainThreadLanguageFeatures._reviveRegExp(indentationRule.decreaseIndentPattern), increaseIndentPattern: MainThreadLanguageFeatures._reviveRegExp(indentationRule.increaseIndentPattern), @@ -529,7 +529,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha }; } - private static _reviveOnEnterRule(onEnterRule: ISerializedOnEnterRule): OnEnterRule { + private static _reviveOnEnterRule(onEnterRule: IOnEnterRuleDto): OnEnterRule { return { beforeText: MainThreadLanguageFeatures._reviveRegExp(onEnterRule.beforeText), afterText: onEnterRule.afterText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.afterText) : undefined, @@ -538,11 +538,11 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha }; } - private static _reviveOnEnterRules(onEnterRules: ISerializedOnEnterRule[]): OnEnterRule[] { + private static _reviveOnEnterRules(onEnterRules: IOnEnterRuleDto[]): OnEnterRule[] { return onEnterRules.map(MainThreadLanguageFeatures._reviveOnEnterRule); } - $setLanguageConfiguration(handle: number, languageId: string, _configuration: ISerializedLanguageConfiguration): void { + $setLanguageConfiguration(handle: number, languageId: string, _configuration: ILanguageConfigurationDto): void { const configuration: LanguageConfiguration = { comments: _configuration.comments, diff --git a/src/vs/workbench/api/browser/mainThreadTask.ts b/src/vs/workbench/api/browser/mainThreadTask.ts index 00c9f5de12..a35f8e69fb 100644 --- a/src/vs/workbench/api/browser/mainThreadTask.ts +++ b/src/vs/workbench/api/browser/mainThreadTask.ts @@ -29,7 +29,7 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { ExtHostContext, MainThreadTaskShape, ExtHostTaskShape, MainContext, IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { TaskDefinitionDTO, TaskExecutionDTO, ProcessExecutionOptionsDTO, TaskPresentationOptionsDTO, - ProcessExecutionDTO, ShellExecutionDTO, ShellExecutionOptionsDTO, CustomExecutionDTO, CustomExecution2DTO, TaskDTO, TaskSourceDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO, + ProcessExecutionDTO, ShellExecutionDTO, ShellExecutionOptionsDTO, CustomExecution2DTO, TaskDTO, TaskSourceDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO, RunOptionsDTO } from 'vs/workbench/api/common/shared/tasks'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; @@ -131,7 +131,7 @@ namespace ProcessExecutionOptionsDTO { } namespace ProcessExecutionDTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO): value is ProcessExecutionDTO { + export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecution2DTO): value is ProcessExecutionDTO { const candidate = value as ProcessExecutionDTO; return candidate && !!candidate.process; } @@ -199,7 +199,7 @@ namespace ShellExecutionOptionsDTO { } namespace ShellExecutionDTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO): value is ShellExecutionDTO { + export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecution2DTO): value is ShellExecutionDTO { const candidate = value as ShellExecutionDTO; return candidate && (!!candidate.commandLine || !!candidate.command); } @@ -230,28 +230,8 @@ namespace ShellExecutionDTO { } } -namespace CustomExecutionDTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO): value is CustomExecutionDTO { - const candidate = value as CustomExecutionDTO; - return candidate && candidate.customExecution === 'customExecution'; - } - - export function from(value: CommandConfiguration): CustomExecutionDTO { - return { - customExecution: 'customExecution' - }; - } - - export function to(value: CustomExecutionDTO): CommandConfiguration { - return { - runtime: RuntimeType.CustomExecution, - presentation: undefined - }; - } -} - namespace CustomExecution2DTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO): value is CustomExecution2DTO { + export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecution2DTO): value is CustomExecution2DTO { const candidate = value as CustomExecution2DTO; return candidate && candidate.customExecution === 'customExecution2'; } @@ -371,8 +351,6 @@ namespace TaskDTO { command = ShellExecutionDTO.to(task.execution); } else if (ProcessExecutionDTO.is(task.execution)) { command = ProcessExecutionDTO.to(task.execution); - } else if (CustomExecutionDTO.is(task.execution)) { - command = CustomExecutionDTO.to(task.execution); } else if (CustomExecution2DTO.is(task.execution)) { command = CustomExecution2DTO.to(task.execution); } @@ -420,7 +398,7 @@ namespace TaskFilterDTO { @extHostNamedCustomer(MainContext.MainThreadTask) export class MainThreadTask implements MainThreadTaskShape { - private readonly _extHostContext: IExtHostContext; + private readonly _extHostContext: IExtHostContext | undefined; private readonly _proxy: ExtHostTaskShape; private readonly _providers: Map; diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index d4e6c1f136..c05c803afb 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -3,14 +3,15 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY, IAvailableShellsRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest } from 'vs/workbench/contrib/terminal/common/terminal'; -import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, ShellLaunchConfigDto, TerminalLaunchConfig, ITerminalDimensionsDto } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, IShellLaunchConfigDto, TerminalLaunchConfig, ITerminalDimensionsDto } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { URI } from 'vs/base/common/uri'; import { StopWatch } from 'vs/base/common/stopwatch'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @extHostNamedCustomer(MainContext.MainThreadTerminalService) export class MainThreadTerminalService implements MainThreadTerminalServiceShape { @@ -21,13 +22,14 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape private readonly _terminalProcesses = new Map>(); private readonly _terminalProcessesReady = new Map void>(); private readonly _terminalOnDidWriteDataListeners = new Map(); - private readonly _terminalOnDidAcceptInputListeners = new Map(); + private _dataEventTracker: TerminalDataEventTracker | undefined; constructor( extHostContext: IExtHostContext, @ITerminalService private readonly _terminalService: ITerminalService, @ITerminalInstanceService readonly terminalInstanceService: ITerminalInstanceService, - @IRemoteAgentService readonly _remoteAgentService: IRemoteAgentService + @IRemoteAgentService readonly _remoteAgentService: IRemoteAgentService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService); this._remoteAuthority = extHostContext.remoteAuthority; @@ -100,11 +102,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape }); } - public $createTerminalRenderer(name: string): Promise { - const instance = this._terminalService.createTerminalRenderer(name); - return Promise.resolve(instance.id); - } - public $show(terminalId: number, preserveFocus: boolean): void { const terminalInstance = this._terminalService.getInstanceFromId(terminalId); if (terminalInstance) { @@ -127,44 +124,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } } - public $terminalRendererWrite(terminalId: number, text: string): void { - const terminalInstance = this._terminalService.getInstanceFromId(terminalId); - if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) { - terminalInstance.write(text); - } - } - - public $terminalRendererSetName(terminalId: number, name: string): void { - const terminalInstance = this._terminalService.getInstanceFromId(terminalId); - if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) { - terminalInstance.setTitle(name, false); - } - } - - public $terminalRendererSetDimensions(terminalId: number, dimensions: ITerminalDimensions): void { - const terminalInstance = this._terminalService.getInstanceFromId(terminalId); - if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) { - terminalInstance.setDimensions(dimensions); - } - } - - public $terminalRendererRegisterOnInputListener(terminalId: number): void { - const terminalInstance = this._terminalService.getInstanceFromId(terminalId); - if (!terminalInstance) { - return; - } - - // Listener already registered - if (this._terminalOnDidAcceptInputListeners.has(terminalId)) { - return; - } - - // Register - const listener = terminalInstance.onRendererInput(data => this._onTerminalRendererInput(terminalId, data)); - this._terminalOnDidAcceptInputListeners.set(terminalId, listener); - terminalInstance.addDisposable(listener); - } - public $sendText(terminalId: number, text: string, addNewLine: boolean): void { const terminalInstance = this._terminalService.getInstanceFromId(terminalId); if (terminalInstance) { @@ -172,6 +131,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } } + /** @deprecated */ public $registerOnDataListener(terminalId: number): void { const terminalInstance = this._terminalService.getInstanceFromId(terminalId); if (!terminalInstance) { @@ -191,14 +151,34 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape terminalInstance.addDisposable(listener); } + public $startSendingDataEvents(): void { + if (!this._dataEventTracker) { + this._dataEventTracker = this._instantiationService.createInstance(TerminalDataEventTracker, (id, data) => { + this._onTerminalData2(id, data); + }); + } + } + + public $stopSendingDataEvents(): void { + if (this._dataEventTracker) { + this._dataEventTracker.dispose(); + this._dataEventTracker = undefined; + } + } + private _onActiveTerminalChanged(terminalId: number | null): void { this._proxy.$acceptActiveTerminalChanged(terminalId); } + /** @deprecated */ private _onTerminalData(terminalId: number, data: string): void { this._proxy.$acceptTerminalProcessData(terminalId, data); } + private _onTerminalData2(terminalId: number, data: string): void { + this._proxy.$acceptTerminalProcessData2(terminalId, data); + } + private _onTitleChanged(terminalId: number, name: string): void { this._proxy.$acceptTerminalTitleChange(terminalId, name); } @@ -207,10 +187,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape this._proxy.$acceptWorkspacePermissionsChanged(isAllowed); } - private _onTerminalRendererInput(terminalId: number, data: string): void { - this._proxy.$acceptTerminalRendererInput(terminalId, data); - } - private _onTerminalDisposed(terminalInstance: ITerminalInstance): void { this._proxy.$acceptTerminalClosed(terminalInstance.id); } @@ -254,7 +230,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } else { this._terminalProcesses.set(proxy.terminalId, Promise.resolve(proxy)); } - const shellLaunchConfigDto: ShellLaunchConfigDto = { + const shellLaunchConfigDto: IShellLaunchConfigDto = { name: request.shellLaunchConfig.name, executable: request.shellLaunchConfig.executable, args: request.shellLaunchConfig.args, @@ -359,7 +335,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape private _onRequestDefaultShellAndArgs(request: IDefaultShellAndArgsRequest): void { if (this._isPrimaryExtHost()) { - this._proxy.$requestDefaultShellAndArgs().then(e => request(e.shell, e.args)); + this._proxy.$requestDefaultShellAndArgs(request.useAutomationShell).then(e => request.callback(e.shell, e.args)); } } @@ -371,3 +347,22 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape return terminal; } } + +/** + * Encapsulates temporary tracking of data events from terminal instances, once disposed all + * listeners are removed. + */ +class TerminalDataEventTracker extends Disposable { + constructor( + private readonly _callback: (id: number, data: string) => void, + @ITerminalService private readonly _terminalService: ITerminalService + ) { + super(); + this._terminalService.terminalInstances.forEach(instance => this._registerInstance(instance)); + this._register(this._terminalService.onInstanceCreated(instance => this._registerInstance(instance))); + } + + private _registerInstance(instance: ITerminalInstance): void { + this._register(instance.onData(e => this._callback(instance.id, e))); + } +} diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index 16b119ee82..71cff6c3bf 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -10,7 +10,6 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { distinct } from 'vs/base/common/arrays'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { isUndefinedOrNull, isNumber } from 'vs/base/common/types'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; import { Registry } from 'vs/platform/registry/common/platform'; @extHostNamedCustomer(MainContext.MainThreadTreeViews) @@ -63,7 +62,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie return Promise.resolve(); } - $setMessage(treeViewId: string, message: string | IMarkdownString): void { + $setMessage(treeViewId: string, message: string): void { const viewer = this.getTreeView(treeViewId); if (viewer) { viewer.message = message; diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts similarity index 91% rename from src/vs/workbench/api/node/extHost.api.impl.ts rename to src/vs/workbench/api/common/extHost.api.impl.ts index ccd8015af7..bbf7378881 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -15,66 +15,60 @@ import { OverviewRulerLane } from 'vs/editor/common/model'; import * as languageConfiguration from 'vs/editor/common/modes/languageConfiguration'; import { score } from 'vs/editor/common/modes/languageSelector'; import * as files from 'vs/platform/files/common/files'; -import { ExtHostContext, IInitData, IMainContext, MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostContext, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostApiCommands } from 'vs/workbench/api/common/extHostApiCommands'; import { ExtHostClipboard } from 'vs/workbench/api/common/extHostClipboard'; -import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostComments } from 'vs/workbench/api/common/extHostComments'; -import { ExtHostConfiguration, ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; -// {{SQL CARBON EDIT}} - Remove ExtHostDebugService -// import { ExtHostDebugService } from 'vs/workbench/api/node/extHostDebugService'; -// {{SQL CARBON EDIT}} - Import product -import product from 'vs/platform/product/node/product'; -import { ExtHostDecorations } from 'vs/workbench/api/common/extHostDecorations'; +import { ExtHostConfigProvider, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics'; import { ExtHostDialogs } from 'vs/workbench/api/common/extHostDialogs'; import { ExtHostDocumentContentProvider } from 'vs/workbench/api/common/extHostDocumentContentProviders'; import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/common/extHostDocumentSaveParticipant'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; -import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { ExtensionActivatedByAPI } from 'vs/workbench/api/common/extHostExtensionActivator'; -import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; +import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { ExtHostFileSystem } from 'vs/workbench/api/common/extHostFileSystem'; import { ExtHostFileSystemEventService } from 'vs/workbench/api/common/extHostFileSystemEventService'; import { ExtHostLanguageFeatures } from 'vs/workbench/api/common/extHostLanguageFeatures'; import { ExtHostLanguages } from 'vs/workbench/api/common/extHostLanguages'; import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService'; import { ExtHostMessageService } from 'vs/workbench/api/common/extHostMessageService'; -import { ExtHostOutputService } from 'vs/workbench/api/common/extHostOutput'; -import { LogOutputChannelFactory } from 'vs/workbench/api/node/extHostOutputService'; +import { IExtHostOutputService } from 'vs/workbench/api/common/extHostOutput'; import { ExtHostProgress } from 'vs/workbench/api/common/extHostProgress'; import { ExtHostQuickOpen } from 'vs/workbench/api/common/extHostQuickOpen'; import { ExtHostSCM } from 'vs/workbench/api/common/extHostSCM'; -import { ExtHostSearch, registerEHSearchProviders } from 'vs/workbench/api/node/extHostSearch'; import { ExtHostStatusBar } from 'vs/workbench/api/common/extHostStatusBar'; -import { ExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; -import { ExtHostTask } from 'vs/workbench/api/node/extHostTask'; -import { ExtHostTerminalService } from 'vs/workbench/api/node/extHostTerminalService'; +import { IExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; +import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors'; import { ExtHostTreeViews } from 'vs/workbench/api/common/extHostTreeViews'; -import { ExtHostDownloadService } from 'vs/workbench/api/node/extHostDownloadService'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { ExtHostUrls } from 'vs/workbench/api/common/extHostUrls'; import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview'; import { ExtHostWindow } from 'vs/workbench/api/common/extHostWindow'; -import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; +import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { throwProposedApiError, checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import * as vscode from 'vscode'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { originalFSPath } from 'vs/base/common/resources'; -import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer'; import { values } from 'vs/base/common/collections'; -import { Schemas } from 'vs/base/common/network'; -import { IURITransformer } from 'vs/base/common/uriIpc'; import { ExtHostEditorInsets } from 'vs/workbench/api/common/extHostCodeInsets'; import { ExtHostLabelService } from 'vs/workbench/api/common/extHostLabelService'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; -import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions'; import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostDecorations } from 'vs/workbench/api/common/extHostDecorations'; +import { IExtHostTask } from 'vs/workbench/api/common/extHostTask'; +import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; +import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; export interface IExtensionApiFactory { (extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode; @@ -91,68 +85,55 @@ function proposedApiFunction(extension: IExtensionDescription, fn: T): T { /** * This method instantiates and returns the extension API surface */ -export function createApiFactory( - initData: IInitData, - rpcProtocol: IMainContext, - extHostWorkspace: ExtHostWorkspace, - extHostConfiguration: ExtHostConfiguration, - extensionService: ExtHostExtensionService, - extHostLogService: ExtHostLogService, - extHostStorage: ExtHostStorage, - uriTransformer: IURITransformer | null -): IExtensionApiFactory { +export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): IExtensionApiFactory { - // bootstrap services - const services = new ServiceCollection(...getSingletonServiceDescriptors()); - const instaService = new InstantiationService(services); + // services + const initData = accessor.get(IExtHostInitDataService); + const extensionService = accessor.get(IExtHostExtensionService); + const extHostWorkspace = accessor.get(IExtHostWorkspace); + const extHostConfiguration = accessor.get(IExtHostConfiguration); + const uriTransformer = accessor.get(IURITransformerService); + const rpcProtocol = accessor.get(IExtHostRpcService); + const extHostStorage = accessor.get(IExtHostStorage); + const extHostLogService = accessor.get(ILogService); - // Addressable instances + // register addressable instances rpcProtocol.set(ExtHostContext.ExtHostLogService, extHostLogService); - const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, new ExtHostDecorations(rpcProtocol)); + rpcProtocol.set(ExtHostContext.ExtHostWorkspace, extHostWorkspace); + rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration); + rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService); + rpcProtocol.set(ExtHostContext.ExtHostStorage, extHostStorage); + + // automatically create and register addressable instances + const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, accessor.get(IExtHostDecorations)); + const extHostDocumentsAndEditors = rpcProtocol.set(ExtHostContext.ExtHostDocumentsAndEditors, accessor.get(IExtHostDocumentsAndEditors)); + const extHostCommands = rpcProtocol.set(ExtHostContext.ExtHostCommands, accessor.get(IExtHostCommands)); + const extHostTerminalService = rpcProtocol.set(ExtHostContext.ExtHostTerminalService, accessor.get(IExtHostTerminalService)); + // const extHostDebugService = rpcProtocol.set(ExtHostContext.ExtHostDebugService, accessor.get(IExtHostDebugService)); {{SQL CARBON EDIT}} remove debug service + const extHostSearch = rpcProtocol.set(ExtHostContext.ExtHostSearch, accessor.get(IExtHostSearch)); + const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, accessor.get(IExtHostTask)); + const extHostOutputService = rpcProtocol.set(ExtHostContext.ExtHostOutputService, accessor.get(IExtHostOutputService)); + + // manually create and register addressable instances const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.environment)); const extHostUrls = rpcProtocol.set(ExtHostContext.ExtHostUrls, new ExtHostUrls(rpcProtocol)); - const extHostDocumentsAndEditors = rpcProtocol.set(ExtHostContext.ExtHostDocumentsAndEditors, new ExtHostDocumentsAndEditors(rpcProtocol)); const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors)); const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService)); const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostLogService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadTextEditors))); const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors)); - const extHostCommands = rpcProtocol.set(ExtHostContext.ExtHostCommands, new ExtHostCommands(rpcProtocol, extHostLogService)); const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService)); - rpcProtocol.set(ExtHostContext.ExtHostDownloadService, new ExtHostDownloadService(rpcProtocol.getProxy(MainContext.MainThreadDownloadService), extHostCommands)); - rpcProtocol.set(ExtHostContext.ExtHostWorkspace, extHostWorkspace); - rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration); const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, initData.environment)); const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol)); const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, uriTransformer, extHostDocuments, extHostCommands, extHostDiagnostics, extHostLogService)); const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures)); const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostDocumentsAndEditors)); const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands)); - const extHostTerminalService = rpcProtocol.set(ExtHostContext.ExtHostTerminalService, new ExtHostTerminalService(rpcProtocol, extHostConfiguration, extHostWorkspace, extHostDocumentsAndEditors, extHostLogService)); - // const extHostDebugService = rpcProtocol.set(ExtHostContext.ExtHostDebugService, instaService.createInstance(ExtHostDebugService, rpcProtocol, extHostWorkspace, extensionService, extHostDocumentsAndEditors, extHostConfiguration, extHostTerminalService, extHostCommands)); {{SQL CARBON EDIT}} remove debug service const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostLogService)); const extHostComment = rpcProtocol.set(ExtHostContext.ExtHostComments, new ExtHostComments(rpcProtocol, extHostCommands, extHostDocuments)); - const extHostSearch = rpcProtocol.set(ExtHostContext.ExtHostSearch, new ExtHostSearch(rpcProtocol, uriTransformer, extHostLogService)); - const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, new ExtHostTask(rpcProtocol, extHostWorkspace, extHostDocumentsAndEditors, extHostConfiguration, extHostTerminalService)); const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol)); - rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService); const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress))); - const extHostOutputService = rpcProtocol.set(ExtHostContext.ExtHostOutputService, new ExtHostOutputService(LogOutputChannelFactory, initData.logsLocation, rpcProtocol)); - rpcProtocol.set(ExtHostContext.ExtHostStorage, extHostStorage); const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHosLabelService, new ExtHostLabelService(rpcProtocol)); - if (initData.remote.isRemote && initData.remote.authority) { - extHostTask.registerTaskSystem(Schemas.vscodeRemote, { - scheme: Schemas.vscodeRemote, - authority: initData.remote.authority, - platform: process.platform - }); - - registerEHSearchProviders(extHostSearch, extHostLogService); - - const cliServer = new CLIServer(extHostCommands); - process.env['VSCODE_IPC_HOOK_CLI'] = cliServer.ipcHandlePath; - } - // Check that no named customers are missing // {{SQL CARBON EDIT}} filter out the services we don't expose const filtered: ProxyIdentifier[] = [ExtHostContext.ExtHostDebugService, ExtHostContext.ExtHostTask]; @@ -276,7 +257,7 @@ export function createApiFactory( return extHostClipboard; }, get shell() { - return extHostTerminalService.getDefaultShell(configProvider); + return extHostTerminalService.getDefaultShell(false, configProvider); }, openExternal(uri: URI) { return extHostWindow.openUri(uri, { allowTunneling: !!initData.remote.isRemote }); @@ -462,8 +443,13 @@ export function createApiFactory( return extHostTerminalService.onDidChangeActiveTerminal(listener, thisArg, disposables); }, onDidChangeTerminalDimensions(listener, thisArg?, disposables?) { + checkProposedApiEnabled(extension); return extHostTerminalService.onDidChangeTerminalDimensions(listener, thisArg, disposables); }, + onDidWriteTerminalData(listener, thisArg?, disposables?) { + checkProposedApiEnabled(extension); + return extHostTerminalService.onDidWriteTerminalData(listener, thisArg, disposables); + }, get state() { return extHostWindow.state; }, @@ -540,16 +526,11 @@ export function createApiFactory( if (typeof nameOrOptions === 'object') { if ('pty' in nameOrOptions) { return extHostTerminalService.createExtensionTerminal(nameOrOptions); - } else { - nameOrOptions.hideFromUser = nameOrOptions.hideFromUser || (nameOrOptions.runInBackground && extension.enableProposedApi); - return extHostTerminalService.createTerminalFromOptions(nameOrOptions); } + return extHostTerminalService.createTerminalFromOptions(nameOrOptions); } return extHostTerminalService.createTerminal(nameOrOptions, shellPath, shellArgs); }, - createTerminalRenderer(name: string): vscode.TerminalRenderer { - return extHostTerminalService.createTerminalRenderer(name); - }, registerTreeDataProvider(viewId: string, treeDataProvider: vscode.TreeDataProvider): vscode.Disposable { return extHostTreeViews.registerTreeDataProvider(viewId, treeDataProvider, extension); }, @@ -810,7 +791,7 @@ export function createApiFactory( return { // {{SQL CARBON EDIT}} - Expose the VS Code version here for extensions that rely on it - version: product.vscodeVersion, + version: initData.vscodeVersion, // namespaces commands, debug, @@ -857,7 +838,6 @@ export function createApiFactory( EventEmitter: Emitter, ExtensionExecutionContext: extHostTypes.ExtensionExecutionContext, ExtensionKind: extHostTypes.ExtensionKind, - CustomExecution: extHostTypes.CustomExecution, CustomExecution2: extHostTypes.CustomExecution2, FileChangeType: extHostTypes.FileChangeType, FileSystemError: extHostTypes.FileSystemError, @@ -922,7 +902,7 @@ export function createApiFactory( class Extension implements vscode.Extension { - private _extensionService: ExtHostExtensionService; + private _extensionService: IExtHostExtensionService; private _identifier: ExtensionIdentifier; readonly id: string; @@ -930,7 +910,7 @@ class Extension implements vscode.Extension { readonly packageJSON: IExtensionDescription; readonly extensionKind: vscode.ExtensionKind; - constructor(extensionService: ExtHostExtensionService, description: IExtensionDescription, kind: extHostTypes.ExtensionKind) { + constructor(extensionService: IExtHostExtensionService, description: IExtensionDescription, kind: extHostTypes.ExtensionKind) { this._extensionService = extensionService; this._identifier = description.identifier; this.id = description.identifier.value; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 836368075c..de80ece9ae 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -79,6 +79,7 @@ export interface IWorkspaceData extends IStaticWorkspaceData { export interface IInitData { version: string; + vscodeVersion: string; // {{SQL CARBON EDIT}} add vscodeVersion commit?: string; parentPid: number; environment: IEnvironment; @@ -135,7 +136,7 @@ export interface MainThreadCommentsShape extends IDisposable { $unregisterCommentController(handle: number): void; $updateCommentControllerFeatures(handle: number, features: CommentProviderFeatures): void; $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, extensionId: ExtensionIdentifier): modes.CommentThread | undefined; - $updateCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, label: string, contextValue: string | undefined, comments: modes.Comment[], collapseState: modes.CommentThreadCollapsibleState): void; + $updateCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, label: string | undefined, contextValue: string | undefined, comments: modes.Comment[], collapseState: modes.CommentThreadCollapsibleState): void; $deleteCommentThread(handle: number, commentThreadHandle: number): void; $onDidCommentThreadsChange(handle: number, event: modes.CommentThreadChangedEvent): void; } @@ -239,7 +240,7 @@ export interface MainThreadTextEditorsShape extends IDisposable { $tryRevealRange(id: string, range: IRange, revealType: TextEditorRevealType): Promise; $trySetSelections(id: string, selections: ISelection[]): Promise; $tryApplyEdits(id: string, modelVersionId: number, edits: ISingleEditOperation[], opts: IApplyEditsOptions): Promise; - $tryApplyWorkspaceEdit(workspaceEditDto: WorkspaceEditDto): Promise; + $tryApplyWorkspaceEdit(workspaceEditDto: IWorkspaceEditDto): Promise; $tryInsertSnippet(id: string, template: string, selections: readonly IRange[], opts: IUndoStopOptions): Promise; $getDiffInformation(id: string): Promise; } @@ -248,7 +249,7 @@ export interface MainThreadTreeViewsShape extends IDisposable { $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean }): void; $refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem }): Promise; $reveal(treeViewId: string, treeItem: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Promise; - $setMessage(treeViewId: string, message: string | IMarkdownString): void; + $setMessage(treeViewId: string, message: string): void; } export interface MainThreadDownloadServiceShape extends IDisposable { @@ -270,28 +271,28 @@ export interface MainThreadKeytarShape extends IDisposable { $findPassword(service: string): Promise; } -export interface ISerializedRegExp { +export interface IRegExpDto { pattern: string; flags?: string; } -export interface ISerializedIndentationRule { - decreaseIndentPattern: ISerializedRegExp; - increaseIndentPattern: ISerializedRegExp; - indentNextLinePattern?: ISerializedRegExp; - unIndentedLinePattern?: ISerializedRegExp; +export interface IIndentationRuleDto { + decreaseIndentPattern: IRegExpDto; + increaseIndentPattern: IRegExpDto; + indentNextLinePattern?: IRegExpDto; + unIndentedLinePattern?: IRegExpDto; } -export interface ISerializedOnEnterRule { - beforeText: ISerializedRegExp; - afterText?: ISerializedRegExp; - oneLineAboveText?: ISerializedRegExp; +export interface IOnEnterRuleDto { + beforeText: IRegExpDto; + afterText?: IRegExpDto; + oneLineAboveText?: IRegExpDto; action: EnterAction; } -export interface ISerializedLanguageConfiguration { +export interface ILanguageConfigurationDto { comments?: CommentRule; brackets?: CharacterPair[]; - wordPattern?: ISerializedRegExp; - indentationRules?: ISerializedIndentationRule; - onEnterRules?: ISerializedOnEnterRule[]; + wordPattern?: IRegExpDto; + indentationRules?: IIndentationRuleDto; + onEnterRules?: IOnEnterRuleDto[]; __electricCharacterSupport?: { brackets?: any; docComment?: { @@ -312,7 +313,7 @@ export interface ISerializedLanguageConfiguration { export type GlobPattern = string | { base: string; pattern: string }; -export interface ISerializedDocumentFilter { +export interface IDocumentFilterDto { $serialized: true; language?: string; scheme?: string; @@ -320,37 +321,37 @@ export interface ISerializedDocumentFilter { exclusive?: boolean; } -export interface ISerializedSignatureHelpProviderMetadata { +export interface ISignatureHelpProviderMetadataDto { readonly triggerCharacters: readonly string[]; readonly retriggerCharacters: readonly string[]; } export interface MainThreadLanguageFeaturesShape extends IDisposable { $unregister(handle: number): void; - $registerDocumentSymbolProvider(handle: number, selector: ISerializedDocumentFilter[], label: string): void; - $registerCodeLensSupport(handle: number, selector: ISerializedDocumentFilter[], eventHandle: number | undefined): void; + $registerDocumentSymbolProvider(handle: number, selector: IDocumentFilterDto[], label: string): void; + $registerCodeLensSupport(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void; $emitCodeLensEvent(eventHandle: number, event?: any): void; - $registerDefinitionSupport(handle: number, selector: ISerializedDocumentFilter[]): void; - $registerDeclarationSupport(handle: number, selector: ISerializedDocumentFilter[]): void; - $registerImplementationSupport(handle: number, selector: ISerializedDocumentFilter[]): void; - $registerTypeDefinitionSupport(handle: number, selector: ISerializedDocumentFilter[]): void; - $registerHoverProvider(handle: number, selector: ISerializedDocumentFilter[]): void; - $registerDocumentHighlightProvider(handle: number, selector: ISerializedDocumentFilter[]): void; - $registerReferenceSupport(handle: number, selector: ISerializedDocumentFilter[]): void; - $registerQuickFixSupport(handle: number, selector: ISerializedDocumentFilter[], supportedKinds?: string[]): void; - $registerDocumentFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], extensionId: ExtensionIdentifier, displayName: string): void; - $registerRangeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], extensionId: ExtensionIdentifier, displayName: string): void; - $registerOnTypeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void; + $registerDefinitionSupport(handle: number, selector: IDocumentFilterDto[]): void; + $registerDeclarationSupport(handle: number, selector: IDocumentFilterDto[]): void; + $registerImplementationSupport(handle: number, selector: IDocumentFilterDto[]): void; + $registerTypeDefinitionSupport(handle: number, selector: IDocumentFilterDto[]): void; + $registerHoverProvider(handle: number, selector: IDocumentFilterDto[]): void; + $registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void; + $registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void; + $registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], supportedKinds?: string[]): void; + $registerDocumentFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void; + $registerRangeFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void; + $registerOnTypeFormattingSupport(handle: number, selector: IDocumentFilterDto[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void; $registerNavigateTypeSupport(handle: number): void; - $registerRenameSupport(handle: number, selector: ISerializedDocumentFilter[], supportsResolveInitialValues: boolean): void; - $registerSuggestSupport(handle: number, selector: ISerializedDocumentFilter[], triggerCharacters: string[], supportsResolveDetails: boolean, extensionId: ExtensionIdentifier): void; - $registerSignatureHelpProvider(handle: number, selector: ISerializedDocumentFilter[], metadata: ISerializedSignatureHelpProviderMetadata): void; - $registerDocumentLinkProvider(handle: number, selector: ISerializedDocumentFilter[], supportsResolve: boolean): void; - $registerDocumentColorProvider(handle: number, selector: ISerializedDocumentFilter[]): void; - $registerFoldingRangeProvider(handle: number, selector: ISerializedDocumentFilter[]): void; - $registerSelectionRangeProvider(handle: number, selector: ISerializedDocumentFilter[]): void; - $registerCallHierarchyProvider(handle: number, selector: ISerializedDocumentFilter[]): void; - $setLanguageConfiguration(handle: number, languageId: string, configuration: ISerializedLanguageConfiguration): void; + $registerRenameSupport(handle: number, selector: IDocumentFilterDto[], supportsResolveInitialValues: boolean): void; + $registerSuggestSupport(handle: number, selector: IDocumentFilterDto[], triggerCharacters: string[], supportsResolveDetails: boolean, extensionId: ExtensionIdentifier): void; + $registerSignatureHelpProvider(handle: number, selector: IDocumentFilterDto[], metadata: ISignatureHelpProviderMetadataDto): void; + $registerDocumentLinkProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean): void; + $registerDocumentColorProvider(handle: number, selector: IDocumentFilterDto[]): void; + $registerFoldingRangeProvider(handle: number, selector: IDocumentFilterDto[]): void; + $registerSelectionRangeProvider(handle: number, selector: IDocumentFilterDto[]): void; + $registerCallHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void; + $setLanguageConfiguration(handle: number, languageId: string, configuration: ILanguageConfigurationDto): void; } export interface MainThreadLanguagesShape extends IDisposable { @@ -398,12 +399,14 @@ export interface TerminalLaunchConfig { export interface MainThreadTerminalServiceShape extends IDisposable { $createTerminal(config: TerminalLaunchConfig): Promise<{ id: number, name: string }>; - $createTerminalRenderer(name: string): Promise; $dispose(terminalId: number): void; $hide(terminalId: number): void; $sendText(terminalId: number, text: string, addNewLine: boolean): void; $show(terminalId: number, preserveFocus: boolean): void; + /** @deprecated */ $registerOnDataListener(terminalId: number): void; + $startSendingDataEvents(): void; + $stopSendingDataEvents(): void; // Process $sendProcessTitle(terminalId: number, title: string): void; @@ -414,12 +417,6 @@ export interface MainThreadTerminalServiceShape extends IDisposable { $sendProcessCwd(terminalId: number, initialCwd: string): void; $sendOverrideDimensions(terminalId: number, dimensions: ITerminalDimensions | undefined): void; $sendResolvedLaunchConfig(terminalId: number, shellLaunchConfig: IShellLaunchConfig): void; - - // Renderer - $terminalRendererSetName(terminalId: number, name: string): void; - $terminalRendererSetDimensions(terminalId: number, dimensions: ITerminalDimensions): void; - $terminalRendererWrite(terminalId: number, text: string): void; - $terminalRendererRegisterOnInputListener(terminalId: number): void; } export interface TransferQuickPickItems extends quickInput.IQuickPickItem { @@ -652,7 +649,7 @@ export interface SCMProviderFeatures { count?: number; commitTemplate?: string; acceptInputCommand?: modes.Command; - statusBarCommands?: CommandDto[]; + statusBarCommands?: ICommandDto[]; } export interface SCMGroupFeatures { @@ -823,9 +820,6 @@ export interface ExtHostTreeViewsShape { $setVisible(treeViewId: string, visible: boolean): void; } -export interface ExtHostDownloadServiceShape { -} - export interface ExtHostWorkspaceShape { $initializeWorkspace(workspace: IWorkspaceData | null): void; $acceptWorkspaceData(workspace: IWorkspaceData | null): void; @@ -932,7 +926,7 @@ export class IdObject { } } -export interface SuggestDataDto { +export interface ISuggestDataDto { a/* label */: string; b/* kind */: modes.CompletionItemKind; c/* detail */?: string; @@ -950,51 +944,51 @@ export interface SuggestDataDto { x?: ChainedCacheId; } -export interface SuggestResultDto { +export interface ISuggestResultDto { x?: number; a: IRange; - b: SuggestDataDto[]; + b: ISuggestDataDto[]; c?: boolean; } -export interface SignatureHelpDto { +export interface ISignatureHelpDto { id: CacheId; signatures: modes.SignatureInformation[]; activeSignature: number; activeParameter: number; } -export interface SignatureHelpContextDto { +export interface ISignatureHelpContextDto { readonly triggerKind: modes.SignatureHelpTriggerKind; readonly triggerCharacter?: string; readonly isRetrigger: boolean; - readonly activeSignatureHelp?: SignatureHelpDto; + readonly activeSignatureHelp?: ISignatureHelpDto; } -export interface LocationDto { +export interface ILocationDto { uri: UriComponents; range: IRange; } -export interface DefinitionLinkDto { +export interface IDefinitionLinkDto { originSelectionRange?: IRange; uri: UriComponents; range: IRange; targetSelectionRange?: IRange; } -export interface WorkspaceSymbolDto extends IdObject { +export interface IWorkspaceSymbolDto extends IdObject { name: string; containerName?: string; kind: modes.SymbolKind; - location: LocationDto; + location: ILocationDto; } -export interface WorkspaceSymbolsDto extends IdObject { - symbols: WorkspaceSymbolDto[]; +export interface IWorkspaceSymbolsDto extends IdObject { + symbols: IWorkspaceSymbolDto[]; } -export interface ResourceFileEditDto { +export interface IResourceFileEditDto { oldUri?: UriComponents; newUri?: UriComponents; options?: { @@ -1005,76 +999,76 @@ export interface ResourceFileEditDto { }; } -export interface ResourceTextEditDto { +export interface IResourceTextEditDto { resource: UriComponents; modelVersionId?: number; edits: modes.TextEdit[]; } -export interface WorkspaceEditDto { - edits: Array; +export interface IWorkspaceEditDto { + edits: Array; // todo@joh reject should go into rename rejectReason?: string; } -export function reviveWorkspaceEditDto(data: WorkspaceEditDto | undefined): modes.WorkspaceEdit { +export function reviveWorkspaceEditDto(data: IWorkspaceEditDto | undefined): modes.WorkspaceEdit { if (data && data.edits) { for (const edit of data.edits) { - if (typeof (edit).resource === 'object') { - (edit).resource = URI.revive((edit).resource); + if (typeof (edit).resource === 'object') { + (edit).resource = URI.revive((edit).resource); } else { - (edit).newUri = URI.revive((edit).newUri); - (edit).oldUri = URI.revive((edit).oldUri); + (edit).newUri = URI.revive((edit).newUri); + (edit).oldUri = URI.revive((edit).oldUri); } } } return data; } -export type CommandDto = ObjectIdentifier & modes.Command; +export type ICommandDto = ObjectIdentifier & modes.Command; -export interface CodeActionDto { +export interface ICodeActionDto { title: string; - edit?: WorkspaceEditDto; + edit?: IWorkspaceEditDto; diagnostics?: IMarkerData[]; - command?: CommandDto; + command?: ICommandDto; kind?: string; isPreferred?: boolean; } -export interface CodeActionListDto { +export interface ICodeActionListDto { cacheId: number; - actions: ReadonlyArray; + actions: ReadonlyArray; } export type CacheId = number; export type ChainedCacheId = [CacheId, CacheId]; -export interface LinksListDto { +export interface ILinksListDto { id?: CacheId; - links: LinkDto[]; + links: ILinkDto[]; } -export interface LinkDto { +export interface ILinkDto { cacheId?: ChainedCacheId; range: IRange; url?: string | UriComponents; tooltip?: string; } -export interface CodeLensListDto { +export interface ICodeLensListDto { cacheId?: number; - lenses: CodeLensDto[]; + lenses: ICodeLensDto[]; } -export interface CodeLensDto { +export interface ICodeLensDto { cacheId?: ChainedCacheId; range: IRange; - command?: CommandDto; + command?: ICommandDto; } -export interface CallHierarchyDto { +export interface ICallHierarchyDto { _id: number; kind: modes.SymbolKind; name: string; @@ -1086,40 +1080,40 @@ export interface CallHierarchyDto { export interface ExtHostLanguageFeaturesShape { $provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise; - $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise; - $resolveCodeLens(handle: number, symbol: CodeLensDto, token: CancellationToken): Promise; + $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise; + $resolveCodeLens(handle: number, symbol: ICodeLensDto, token: CancellationToken): Promise; $releaseCodeLenses(handle: number, id: number): void; - $provideDefinition(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; - $provideDeclaration(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; - $provideImplementation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; - $provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $provideDefinition(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $provideDeclaration(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $provideImplementation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; - $provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise; - $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise; + $provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise; + $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise; $releaseCodeActions(handle: number, cacheId: number): void; $provideDocumentFormattingEdits(handle: number, resource: UriComponents, options: modes.FormattingOptions, token: CancellationToken): Promise; $provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange, options: modes.FormattingOptions, token: CancellationToken): Promise; $provideOnTypeFormattingEdits(handle: number, resource: UriComponents, position: IPosition, ch: string, options: modes.FormattingOptions, token: CancellationToken): Promise; - $provideWorkspaceSymbols(handle: number, search: string, token: CancellationToken): Promise; - $resolveWorkspaceSymbol(handle: number, symbol: WorkspaceSymbolDto, token: CancellationToken): Promise; + $provideWorkspaceSymbols(handle: number, search: string, token: CancellationToken): Promise; + $resolveWorkspaceSymbol(handle: number, symbol: IWorkspaceSymbolDto, token: CancellationToken): Promise; $releaseWorkspaceSymbols(handle: number, id: number): void; - $provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string, token: CancellationToken): Promise; + $provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string, token: CancellationToken): Promise; $resolveRenameLocation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; - $provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise; - $resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, id: ChainedCacheId, token: CancellationToken): Promise; + $provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise; + $resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, id: ChainedCacheId, token: CancellationToken): Promise; $releaseCompletionItems(handle: number, id: number): void; - $provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: modes.SignatureHelpContext, token: CancellationToken): Promise; + $provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: modes.SignatureHelpContext, token: CancellationToken): Promise; $releaseSignatureHelp(handle: number, id: number): void; - $provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise; - $resolveDocumentLink(handle: number, id: ChainedCacheId, token: CancellationToken): Promise; + $provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise; + $resolveDocumentLink(handle: number, id: ChainedCacheId, token: CancellationToken): Promise; $releaseDocumentLinks(handle: number, id: number): void; $provideDocumentColors(handle: number, resource: UriComponents, token: CancellationToken): Promise; $provideColorPresentations(handle: number, resource: UriComponents, colorInfo: IRawColorInfo, token: CancellationToken): Promise; $provideFoldingRanges(handle: number, resource: UriComponents, context: modes.FoldingContext, token: CancellationToken): Promise; $provideSelectionRanges(handle: number, resource: UriComponents, positions: IPosition[], token: CancellationToken): Promise; - $provideCallHierarchyItem(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; - $resolveCallHierarchyItem(handle: number, item: callHierarchy.CallHierarchyItem, direction: callHierarchy.CallHierarchyDirection, token: CancellationToken): Promise<[CallHierarchyDto, modes.Location[]][]>; + $provideCallHierarchyItem(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $resolveCallHierarchyItem(handle: number, item: callHierarchy.CallHierarchyItem, direction: callHierarchy.CallHierarchyDirection, token: CancellationToken): Promise<[ICallHierarchyDto, modes.Location[]][]>; } export interface ExtHostQuickOpenShape { @@ -1133,7 +1127,7 @@ export interface ExtHostQuickOpenShape { $onDidHide(sessionId: number): void; } -export interface ShellLaunchConfigDto { +export interface IShellLaunchConfigDto { name?: string; executable?: string; args?: string[] | string; @@ -1161,12 +1155,13 @@ export interface ExtHostTerminalServiceShape { $acceptTerminalOpened(id: number, name: string): void; $acceptActiveTerminalChanged(id: number | null): void; $acceptTerminalProcessId(id: number, processId: number): void; + /** @deprecated */ $acceptTerminalProcessData(id: number, data: string): void; - $acceptTerminalRendererInput(id: number, data: string): void; + $acceptTerminalProcessData2(id: number, data: string): void; $acceptTerminalTitleChange(id: number, name: string): void; $acceptTerminalDimensions(id: number, cols: number, rows: number): void; $acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): void; - $spawnExtHostProcess(id: number, shellLaunchConfig: ShellLaunchConfigDto, activeWorkspaceRootUri: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void; + $spawnExtHostProcess(id: number, shellLaunchConfig: IShellLaunchConfigDto, activeWorkspaceRootUri: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void; $startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): void; $acceptProcessInput(id: number, data: string): void; $acceptProcessResize(id: number, cols: number, rows: number): void; @@ -1176,7 +1171,7 @@ export interface ExtHostTerminalServiceShape { $acceptProcessRequestLatency(id: number): number; $acceptWorkspacePermissionsChanged(isAllowed: boolean): void; $requestAvailableShells(): Promise; - $requestDefaultShellAndArgs(): Promise; + $requestDefaultShellAndArgs(useAutomationShell: boolean): Promise; } export interface ExtHostSCMShape { @@ -1254,8 +1249,8 @@ export interface ExtHostDebugServiceShape { $startDASession(handle: number, session: IDebugSessionDto): Promise; $stopDASession(handle: number): Promise; $sendDAMessage(handle: number, message: DebugProtocol.ProtocolMessage): void; - $resolveDebugConfiguration(handle: number, folder: UriComponents | undefined, debugConfiguration: IConfig): Promise; - $provideDebugConfigurations(handle: number, folder: UriComponents | undefined): Promise; + $resolveDebugConfiguration(handle: number, folder: UriComponents | undefined, debugConfiguration: IConfig, token: CancellationToken): Promise; + $provideDebugConfigurations(handle: number, folder: UriComponents | undefined, token: CancellationToken): Promise; $legacyDebugAdapterExecutable(handle: number, folderUri: UriComponents | undefined): Promise; // TODO@AW legacy $provideDebugAdapter(handle: number, session: IDebugSessionDto): Promise; $acceptDebugSessionStarted(session: IDebugSessionDto): void; @@ -1360,7 +1355,6 @@ export const ExtHostContext = { ExtHostDocumentSaveParticipant: createExtId('ExtHostDocumentSaveParticipant'), ExtHostEditors: createExtId('ExtHostEditors'), ExtHostTreeViews: createExtId('ExtHostTreeViews'), - ExtHostDownloadService: createExtId('ExtHostDownloadService'), ExtHostFileSystem: createExtId('ExtHostFileSystem'), ExtHostFileSystemEventService: createExtId('ExtHostFileSystemEventService'), ExtHostLanguageFeatures: createExtId('ExtHostLanguageFeatures'), diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index afb23997a7..52fd9c273e 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -8,7 +8,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import * as vscode from 'vscode'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as types from 'vs/workbench/api/common/extHostTypes'; -import { IRawColorInfo, WorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; +import { IRawColorInfo, IWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { ISingleEditOperation } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import * as search from 'vs/workbench/contrib/search/common/search'; @@ -135,7 +135,7 @@ export class ExtHostApiCommands { description: 'Execute code action provider.', args: [ { name: 'uri', description: 'Uri of a text document', constraint: URI }, - { name: 'range', description: 'Range in a text document', constraint: types.Range }, + { name: 'rangeOrSelection', description: 'Range in a text document. Some refactoring provider requires Selection object.', constraint: types.Range }, { name: 'kind', description: '(optional) Code action kind to return code actions for', constraint: (value: any) => !value || typeof value.value === 'string' }, ], returns: 'A promise that resolves to an array of Command-instances.' @@ -362,7 +362,7 @@ export class ExtHostApiCommands { position: position && typeConverters.Position.from(position), newName }; - return this._commands.executeCommand('_executeDocumentRenameProvider', args).then(value => { + return this._commands.executeCommand('_executeDocumentRenameProvider', args).then(value => { if (!value) { return undefined; } @@ -470,20 +470,22 @@ export class ExtHostApiCommands { return res; } - detail: string; - range: vscode.Range; - selectionRange: vscode.Range; - children: vscode.DocumentSymbol[]; - containerName: string; + detail!: string; + range!: vscode.Range; + selectionRange!: vscode.Range; + children!: vscode.DocumentSymbol[]; + containerName!: string; } return value.map(MergedInfo.to); }); } - private _executeCodeActionProvider(resource: URI, range: types.Range, kind?: string): Promise<(vscode.CodeAction | vscode.Command | undefined)[] | undefined> { + private _executeCodeActionProvider(resource: URI, rangeOrSelection: types.Range | types.Selection, kind?: string): Promise<(vscode.CodeAction | vscode.Command | undefined)[] | undefined> { const args = { resource, - range: typeConverters.Range.from(range), + rangeOrSelection: types.Selection.isSelection(rangeOrSelection) + ? typeConverters.Selection.from(rangeOrSelection) + : typeConverters.Range.from(rangeOrSelection), kind }; return this._commands.executeCommand('_executeCodeActionProvider', args) @@ -504,6 +506,7 @@ export class ExtHostApiCommands { if (codeAction.command) { ret.command = this._commands.converter.fromInternal(codeAction.command); } + ret.isPreferred = codeAction.isPreferred; return ret; } })); diff --git a/src/vs/workbench/api/common/extHostCodeInsets.ts b/src/vs/workbench/api/common/extHostCodeInsets.ts index 65b319fc8c..459a7e52cd 100644 --- a/src/vs/workbench/api/common/extHostCodeInsets.ts +++ b/src/vs/workbench/api/common/extHostCodeInsets.ts @@ -63,7 +63,7 @@ export class ExtHostEditorInsets implements ExtHostEditorInsetsShape { private readonly _uuid = generateUuid(); private _html: string = ''; - private _options: vscode.WebviewOptions; + private _options: vscode.WebviewOptions = Object.create(null); toWebviewResource(resource: vscode.Uri): vscode.Uri { return toWebviewResource(that._initData, this._uuid, resource); diff --git a/src/vs/workbench/api/common/extHostCommands.ts b/src/vs/workbench/api/common/extHostCommands.ts index bdb10ed50d..7db7439f55 100644 --- a/src/vs/workbench/api/common/extHostCommands.ts +++ b/src/vs/workbench/api/common/extHostCommands.ts @@ -8,7 +8,7 @@ import { ICommandHandlerDescription, ICommandEvent } from 'vs/platform/commands/ import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import * as extHostTypeConverter from 'vs/workbench/api/common/extHostTypeConverters'; import { cloneAndChange } from 'vs/base/common/objects'; -import { MainContext, MainThreadCommandsShape, ExtHostCommandsShape, ObjectIdentifier, IMainContext, CommandDto } from './extHost.protocol'; +import { MainContext, MainThreadCommandsShape, ExtHostCommandsShape, ObjectIdentifier, ICommandDto } from './extHost.protocol'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import * as modes from 'vs/editor/common/modes'; import * as vscode from 'vscode'; @@ -19,6 +19,8 @@ import { Position } from 'vs/editor/common/core/position'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; interface CommandHandler { callback: Function; @@ -32,6 +34,8 @@ export interface ArgumentProcessor { export class ExtHostCommands implements ExtHostCommandsShape { + readonly _serviceBrand: any; + private readonly _onDidExecuteCommand: Emitter; readonly onDidExecuteCommand: Event; @@ -42,10 +46,10 @@ export class ExtHostCommands implements ExtHostCommandsShape { private readonly _argumentProcessors: ArgumentProcessor[]; constructor( - mainContext: IMainContext, - logService: ILogService + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @ILogService logService: ILogService ) { - this._proxy = mainContext.getProxy(MainContext.MainThreadCommands); + this._proxy = extHostRpc.getProxy(MainContext.MainThreadCommands); this._onDidExecuteCommand = new Emitter({ onFirstListenerDidAdd: () => this._proxy.$registerCommandListener(), onLastListenerRemove: () => this._proxy.$unregisterCommandListener(), @@ -228,13 +232,13 @@ export class CommandsConverter { this._commands.registerCommand(true, this._delegatingCommandId, this._executeConvertedCommand, this); } - toInternal(command: vscode.Command | undefined, disposables: DisposableStore): CommandDto | undefined { + toInternal(command: vscode.Command | undefined, disposables: DisposableStore): ICommandDto | undefined { if (!command) { return undefined; } - const result: CommandDto = { + const result: ICommandDto = { $ident: undefined, id: command.command, title: command.title, @@ -282,3 +286,6 @@ export class CommandsConverter { } } + +export interface IExtHostCommands extends ExtHostCommands { } +export const IExtHostCommands = createDecorator('IExtHostCommands'); diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index b6e6539604..0809490625 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -253,13 +253,13 @@ export class ExtHostCommentThread implements vscode.CommentThread { return this._range; } - private _label: string; + private _label: string | undefined; - get label(): string { + get label(): string | undefined { return this._label; } - set label(label: string) { + set label(label: string | undefined) { this._label = label; this._onDidUpdateCommentThread.fire(); } diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 33cfce44b6..30c26aa39b 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -7,8 +7,8 @@ import { mixin, deepClone } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import * as vscode from 'vscode'; -import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; -import { ExtHostConfigurationShape, MainThreadConfigurationShape, IWorkspaceConfigurationChangeEventData, IConfigurationInitData } from './extHost.protocol'; +import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; +import { ExtHostConfigurationShape, MainThreadConfigurationShape, IWorkspaceConfigurationChangeEventData, IConfigurationInitData, MainContext } from './extHost.protocol'; import { ConfigurationTarget as ExtHostConfigurationTarget } from './extHostTypes'; import { IConfigurationData, ConfigurationTarget, IConfigurationModel } from 'vs/platform/configuration/common/configuration'; import { Configuration, ConfigurationChangeEvent, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; @@ -18,6 +18,8 @@ import { ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/confi import { isObject } from 'vs/base/common/types'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { Barrier } from 'vs/base/common/async'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; function lookUp(tree: any, key: string) { if (key) { @@ -40,13 +42,18 @@ type ConfigurationInspect = { export class ExtHostConfiguration implements ExtHostConfigurationShape { + readonly _serviceBrand: any; + private readonly _proxy: MainThreadConfigurationShape; private readonly _extHostWorkspace: ExtHostWorkspace; private readonly _barrier: Barrier; private _actual: ExtHostConfigProvider | null; - constructor(proxy: MainThreadConfigurationShape, extHostWorkspace: ExtHostWorkspace) { - this._proxy = proxy; + constructor( + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @IExtHostWorkspace extHostWorkspace: IExtHostWorkspace + ) { + this._proxy = extHostRpc.getProxy(MainContext.MainThreadConfiguration); this._extHostWorkspace = extHostWorkspace; this._barrier = new Barrier(); this._actual = null; @@ -274,3 +281,6 @@ export class ExtHostConfigProvider { return new ConfigurationModel(model.contents, model.keys, model.overrides).freeze(); } } + +export const IExtHostConfiguration = createDecorator('IExtHostConfiguration'); +export interface IExtHostConfiguration extends ExtHostConfiguration { } diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts new file mode 100644 index 0000000000..737960abea --- /dev/null +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { ExtHostDebugServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import * as vscode from 'vscode'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IExtHostDebugService = createDecorator('IExtHostDebugService'); + +export interface IExtHostDebugService extends ExtHostDebugServiceShape { + + readonly _serviceBrand: any; + + onDidStartDebugSession: Event; + onDidTerminateDebugSession: Event; + onDidChangeActiveDebugSession: Event; + activeDebugSession: vscode.DebugSession | undefined; + activeDebugConsole: vscode.DebugConsole; + onDidReceiveDebugSessionCustomEvent: Event; + onDidChangeBreakpoints: Event; + breakpoints: vscode.Breakpoint[]; + + addBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise; + removeBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise; + startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, parentSession?: vscode.DebugSession): Promise; + registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider): vscode.Disposable; + registerDebugAdapterDescriptorFactory(extension: IExtensionDescription, type: string, factory: vscode.DebugAdapterDescriptorFactory): vscode.Disposable; + registerDebugAdapterTrackerFactory(type: string, factory: vscode.DebugAdapterTrackerFactory): vscode.Disposable; +} + diff --git a/src/vs/workbench/api/common/extHostDecorations.ts b/src/vs/workbench/api/common/extHostDecorations.ts index 9cb2528cf6..3af0d8259d 100644 --- a/src/vs/workbench/api/common/extHostDecorations.ts +++ b/src/vs/workbench/api/common/extHostDecorations.ts @@ -5,26 +5,31 @@ import * as vscode from 'vscode'; import { URI } from 'vs/base/common/uri'; -import { MainContext, IMainContext, ExtHostDecorationsShape, MainThreadDecorationsShape, DecorationData, DecorationRequest, DecorationReply } from 'vs/workbench/api/common/extHost.protocol'; +import { MainContext, ExtHostDecorationsShape, MainThreadDecorationsShape, DecorationData, DecorationRequest, DecorationReply } from 'vs/workbench/api/common/extHost.protocol'; import { Disposable } from 'vs/workbench/api/common/extHostTypes'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { asArray } from 'vs/base/common/arrays'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; interface ProviderData { provider: vscode.DecorationProvider; extensionId: ExtensionIdentifier; } -export class ExtHostDecorations implements ExtHostDecorationsShape { +export class ExtHostDecorations implements IExtHostDecorations { private static _handlePool = 0; + readonly _serviceBrand: undefined; private readonly _provider = new Map(); private readonly _proxy: MainThreadDecorationsShape; - constructor(mainContext: IMainContext) { - this._proxy = mainContext.getProxy(MainContext.MainThreadDecorations); + constructor( + @IExtHostRpcService extHostRpc: IExtHostRpcService, + ) { + this._proxy = extHostRpc.getProxy(MainContext.MainThreadDecorations); } registerDecorationProvider(provider: vscode.DecorationProvider, extensionId: ExtensionIdentifier): vscode.Disposable { @@ -70,3 +75,6 @@ export class ExtHostDecorations implements ExtHostDecorationsShape { }); } } + +export const IExtHostDecorations = createDecorator('IExtHostDecorations'); +export interface IExtHostDecorations extends ExtHostDecorations, ExtHostDecorationsShape { } diff --git a/src/vs/workbench/api/common/extHostDocumentContentProviders.ts b/src/vs/workbench/api/common/extHostDocumentContentProviders.ts index 349763bce3..2377e139f6 100644 --- a/src/vs/workbench/api/common/extHostDocumentContentProviders.ts +++ b/src/vs/workbench/api/common/extHostDocumentContentProviders.ts @@ -50,7 +50,7 @@ export class ExtHostDocumentContentProvider implements ExtHostDocumentContentPro } if (this._documentsAndEditors.getDocument(uri)) { this.$provideTextDocumentContent(handle, uri).then(value => { - if (!value) { + if (!value && typeof value !== 'string') { return; } diff --git a/src/vs/workbench/api/common/extHostDocumentData.ts b/src/vs/workbench/api/common/extHostDocumentData.ts index 66f82ecb68..b4a02faa2c 100644 --- a/src/vs/workbench/api/common/extHostDocumentData.ts +++ b/src/vs/workbench/api/common/extHostDocumentData.ts @@ -26,7 +26,7 @@ export class ExtHostDocumentData extends MirrorTextModel { private _proxy: MainThreadDocumentsShape; private _languageId: string; private _isDirty: boolean; - private _document: vscode.TextDocument; + private _document?: vscode.TextDocument; private _textLines: vscode.TextLine[] = []; private _isDisposed: boolean = false; diff --git a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts index 26269a732a..a5339ea50d 100644 --- a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts +++ b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts @@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; import { sequence } from 'vs/base/common/async'; import { illegalState } from 'vs/base/common/errors'; -import { ExtHostDocumentSaveParticipantShape, MainThreadTextEditorsShape, ResourceTextEditDto } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostDocumentSaveParticipantShape, MainThreadTextEditorsShape, IResourceTextEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { TextEdit } from 'vs/workbench/api/common/extHostTypes'; import { Range, TextDocumentSaveReason, EndOfLine } from 'vs/workbench/api/common/extHostTypeConverters'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; @@ -142,7 +142,7 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic }).then(values => { - const resourceEdit: ResourceTextEditDto = { + const resourceEdit: IResourceTextEditDto = { resource: document.uri, edits: [] }; @@ -153,7 +153,7 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic resourceEdit.edits.push({ range: range && Range.from(range), text: newText, - eol: EndOfLine.from(newEol) + eol: newEol && EndOfLine.from(newEol) }); } } diff --git a/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts b/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts index 4014526d65..35bfa5bc86 100644 --- a/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts +++ b/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts @@ -7,17 +7,21 @@ import * as assert from 'vs/base/common/assert'; import { Emitter, Event } from 'vs/base/common/event'; import { dispose } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta, IMainContext, MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; import { ExtHostTextEditor } from 'vs/workbench/api/common/extHostTextEditor'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { Disposable } from 'vs/workbench/api/common/extHostTypes'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsShape { + readonly _serviceBrand: any; + private _disposables: Disposable[] = []; - private _activeEditorId: string | null; + private _activeEditorId: string | null = null; private readonly _editors = new Map(); private readonly _documents = new Map(); @@ -33,7 +37,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha readonly onDidChangeActiveTextEditor: Event = this._onDidChangeActiveTextEditor.event; constructor( - private readonly _mainContext: IMainContext, + @IExtHostRpcService private readonly _extHostRpc: IExtHostRpcService, ) { } dispose() { @@ -64,7 +68,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha assert.ok(!this._documents.has(resource.toString()), `document '${resource} already exists!'`); const documentData = new ExtHostDocumentData( - this._mainContext.getProxy(MainContext.MainThreadDocuments), + this._extHostRpc.getProxy(MainContext.MainThreadDocuments), resource, data.lines, data.EOL, @@ -95,7 +99,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha const documentData = this._documents.get(resource.toString())!; const editor = new ExtHostTextEditor( - this._mainContext.getProxy(MainContext.MainThreadTextEditors), + this._extHostRpc.getProxy(MainContext.MainThreadTextEditors), data.id, documentData, data.selections.map(typeConverters.Selection.to), @@ -159,3 +163,6 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha return result; } } + +export interface IExtHostDocumentsAndEditors extends ExtHostDocumentsAndEditors { } +export const IExtHostDocumentsAndEditors = createDecorator('IExtHostDocumentsAndEditors'); diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts new file mode 100644 index 0000000000..12dfb21ea4 --- /dev/null +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -0,0 +1,779 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import * as path from 'vs/base/common/path'; +import { originalFSPath } from 'vs/base/common/resources'; +import { Barrier } from 'vs/base/common/async'; +import { dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { TernarySearchTree } from 'vs/base/common/map'; +import { URI } from 'vs/base/common/uri'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ExtHostExtensionServiceShape, IInitData, MainContext, MainThreadExtensionServiceShape, MainThreadTelemetryShape, MainThreadWorkspaceShape, IResolveAuthorityResult } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostConfiguration, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; +import { ActivatedExtension, EmptyExtension, ExtensionActivatedByAPI, ExtensionActivatedByEvent, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionContext, IExtensionModule, HostExtension, ExtensionActivationTimesFragment } from 'vs/workbench/api/common/extHostExtensionActivator'; +import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService'; +import { ExtHostStorage, IExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; +import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; +import { ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import * as errors from 'vs/base/common/errors'; +import * as vscode from 'vscode'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { Schemas } from 'vs/base/common/network'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { ExtensionMemento } from 'vs/workbench/api/common/extHostMemento'; +import { RemoteAuthorityResolverError, ExtensionExecutionContext } from 'vs/workbench/api/common/extHostTypes'; +import { ResolvedAuthority, ResolvedOptions } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; + +interface ITestRunner { + /** Old test runner API, as exported from `vscode/lib/testrunner` */ + run(testsRoot: string, clb: (error: Error, failures?: number) => void): void; +} + +interface INewTestRunner { + /** New test runner API, as explained in the extension test doc */ + run(): Promise; +} + +export const IHostUtils = createDecorator('IHostUtils'); + +export interface IHostUtils { + _serviceBrand: undefined; + exit(code?: number): void; + exists(path: string): Promise; + realpath(path: string): Promise; +} + +type TelemetryActivationEventFragment = { + id: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; + name: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; + extensionVersion: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; + publisherDisplayName: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + activationEvents: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + isBuiltin: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + reason: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; +}; + +export abstract class AbstractExtHostExtensionService implements ExtHostExtensionServiceShape { + + readonly _serviceBrand: any; + + private static readonly WORKSPACE_CONTAINS_TIMEOUT = 7000; + + protected readonly _hostUtils: IHostUtils; + protected readonly _initData: IInitData; + protected readonly _extHostContext: IExtHostRpcService; + protected readonly _instaService: IInstantiationService; + protected readonly _extHostWorkspace: ExtHostWorkspace; + protected readonly _extHostConfiguration: ExtHostConfiguration; + protected readonly _extHostLogService: ExtHostLogService; + + protected readonly _mainThreadWorkspaceProxy: MainThreadWorkspaceShape; + protected readonly _mainThreadTelemetryProxy: MainThreadTelemetryShape; + protected readonly _mainThreadExtensionsProxy: MainThreadExtensionServiceShape; + + private readonly _almostReadyToRunExtensions: Barrier; + private readonly _readyToStartExtensionHost: Barrier; + private readonly _readyToRunExtensions: Barrier; + protected readonly _registry: ExtensionDescriptionRegistry; + private readonly _storage: ExtHostStorage; + private readonly _storagePath: IExtensionStoragePaths; + private readonly _activator: ExtensionsActivator; + private _extensionPathIndex: Promise> | null; + + private readonly _resolvers: { [authorityPrefix: string]: vscode.RemoteAuthorityResolver; }; + + private _started: boolean; + + private readonly _disposables: DisposableStore; + + constructor( + @IInstantiationService instaService: IInstantiationService, + @IHostUtils hostUtils: IHostUtils, + @IExtHostRpcService extHostContext: IExtHostRpcService, + @IExtHostWorkspace extHostWorkspace: IExtHostWorkspace, + @IExtHostConfiguration extHostConfiguration: IExtHostConfiguration, + @ILogService extHostLogService: ExtHostLogService, + @IExtHostInitDataService initData: IExtHostInitDataService, + @IExtensionStoragePaths storagePath: IExtensionStoragePaths + ) { + this._hostUtils = hostUtils; + this._extHostContext = extHostContext; + this._initData = initData; + + this._extHostWorkspace = extHostWorkspace; + this._extHostConfiguration = extHostConfiguration; + this._extHostLogService = extHostLogService; + this._disposables = new DisposableStore(); + + this._mainThreadWorkspaceProxy = this._extHostContext.getProxy(MainContext.MainThreadWorkspace); + this._mainThreadTelemetryProxy = this._extHostContext.getProxy(MainContext.MainThreadTelemetry); + this._mainThreadExtensionsProxy = this._extHostContext.getProxy(MainContext.MainThreadExtensionService); + + this._almostReadyToRunExtensions = new Barrier(); + this._readyToStartExtensionHost = new Barrier(); + this._readyToRunExtensions = new Barrier(); + this._registry = new ExtensionDescriptionRegistry(this._initData.extensions); + this._storage = new ExtHostStorage(this._extHostContext); + this._storagePath = storagePath; + + this._instaService = instaService.createChild(new ServiceCollection( + [IExtHostStorage, this._storage] + )); + + const hostExtensions = new Set(); + this._initData.hostExtensions.forEach((extensionId) => hostExtensions.add(ExtensionIdentifier.toKey(extensionId))); + + this._activator = new ExtensionsActivator(this._registry, this._initData.resolvedExtensions, this._initData.hostExtensions, { + onExtensionActivationError: (extensionId: ExtensionIdentifier, error: ExtensionActivationError): void => { + this._mainThreadExtensionsProxy.$onExtensionActivationError(extensionId, error); + }, + + actualActivateExtension: async (extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise => { + if (hostExtensions.has(ExtensionIdentifier.toKey(extensionId))) { + const activationEvent = (reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : null); + await this._mainThreadExtensionsProxy.$activateExtension(extensionId, activationEvent); + return new HostExtension(); + } + const extensionDescription = this._registry.getExtensionDescription(extensionId)!; + return this._activateExtension(extensionDescription, reason); + } + }); + this._extensionPathIndex = null; + this._resolvers = Object.create(null); + this._started = false; + } + + public async initialize(): Promise { + try { + + await this._beforeAlmostReadyToRunExtensions(); + this._almostReadyToRunExtensions.open(); + + await this._extHostWorkspace.waitForInitializeCall(); + this._readyToStartExtensionHost.open(); + + if (this._initData.autoStart) { + this._startExtensionHost(); + } + } catch (err) { + errors.onUnexpectedError(err); + } + } + + protected abstract _beforeAlmostReadyToRunExtensions(): Promise; + + public async deactivateAll(): Promise { + let allPromises: Promise[] = []; + try { + const allExtensions = this._registry.getAllExtensionDescriptions(); + const allExtensionsIds = allExtensions.map(ext => ext.identifier); + const activatedExtensions = allExtensionsIds.filter(id => this.isActivated(id)); + + allPromises = activatedExtensions.map((extensionId) => { + return this._deactivate(extensionId); + }); + } catch (err) { + // TODO: write to log once we have one + } + await allPromises; + } + + public isActivated(extensionId: ExtensionIdentifier): boolean { + if (this._readyToRunExtensions.isOpen()) { + return this._activator.isActivated(extensionId); + } + return false; + } + + private _activateByEvent(activationEvent: string, startup: boolean): Promise { + const reason = new ExtensionActivatedByEvent(startup, activationEvent); + return this._activator.activateByEvent(activationEvent, reason); + } + + private _activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { + return this._activator.activateById(extensionId, reason); + } + + public activateByIdWithErrors(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { + return this._activateById(extensionId, reason).then(() => { + const extension = this._activator.getActivatedExtension(extensionId); + if (extension.activationFailed) { + // activation failed => bubble up the error as the promise result + return Promise.reject(extension.activationFailedError); + } + return undefined; + }); + } + + public getExtensionRegistry(): Promise { + return this._readyToRunExtensions.wait().then(_ => this._registry); + } + + public getExtensionExports(extensionId: ExtensionIdentifier): IExtensionAPI | null | undefined { + if (this._readyToRunExtensions.isOpen()) { + return this._activator.getActivatedExtension(extensionId).exports; + } else { + return null; + } + } + + // create trie to enable fast 'filename -> extension id' look up + public getExtensionPathIndex(): Promise> { + if (!this._extensionPathIndex) { + const tree = TernarySearchTree.forPaths(); + const extensions = this._registry.getAllExtensionDescriptions().map(ext => { + if (!ext.main) { + return undefined; + } + return this._hostUtils.realpath(ext.extensionLocation.fsPath).then(value => tree.set(URI.file(value).fsPath, ext)); + }); + this._extensionPathIndex = Promise.all(extensions).then(() => tree); + } + return this._extensionPathIndex; + } + + private _deactivate(extensionId: ExtensionIdentifier): Promise { + let result = Promise.resolve(undefined); + + if (!this._readyToRunExtensions.isOpen()) { + return result; + } + + if (!this._activator.isActivated(extensionId)) { + return result; + } + + const extension = this._activator.getActivatedExtension(extensionId); + if (!extension) { + return result; + } + + // call deactivate if available + try { + if (typeof extension.module.deactivate === 'function') { + result = Promise.resolve(extension.module.deactivate()).then(undefined, (err) => { + // TODO: Do something with err if this is not the shutdown case + return Promise.resolve(undefined); + }); + } + } catch (err) { + // TODO: Do something with err if this is not the shutdown case + } + + // clean up subscriptions + try { + dispose(extension.subscriptions); + } catch (err) { + // TODO: Do something with err if this is not the shutdown case + } + + return result; + } + + // --- impl + + private _activateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise { + this._mainThreadExtensionsProxy.$onWillActivateExtension(extensionDescription.identifier); + return this._doActivateExtension(extensionDescription, reason).then((activatedExtension) => { + const activationTimes = activatedExtension.activationTimes; + const activationEvent = (reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : null); + this._mainThreadExtensionsProxy.$onDidActivateExtension(extensionDescription.identifier, activationTimes.startup, activationTimes.codeLoadingTime, activationTimes.activateCallTime, activationTimes.activateResolvedTime, activationEvent); + this._logExtensionActivationTimes(extensionDescription, reason, 'success', activationTimes); + return activatedExtension; + }, (err) => { + this._logExtensionActivationTimes(extensionDescription, reason, 'failure'); + throw err; + }); + } + + private _logExtensionActivationTimes(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason, outcome: string, activationTimes?: ExtensionActivationTimes) { + const event = getTelemetryActivationEvent(extensionDescription, reason); + type ExtensionActivationTimesClassification = { + outcome: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + } & TelemetryActivationEventFragment & ExtensionActivationTimesFragment; + + type ExtensionActivationTimesEvent = { + outcome: string + } & ActivationTimesEvent & TelemetryActivationEvent; + + type ActivationTimesEvent = { + startup?: boolean; + codeLoadingTime?: number; + activateCallTime?: number; + activateResolvedTime?: number; + }; + + this._mainThreadTelemetryProxy.$publicLog2('extensionActivationTimes', { + ...event, + ...(activationTimes || {}), + outcome + }); + } + + private _doActivateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise { + const event = getTelemetryActivationEvent(extensionDescription, reason); + type ActivatePluginClassification = {} & TelemetryActivationEventFragment; + this._mainThreadTelemetryProxy.$publicLog2('activatePlugin', event); + if (!extensionDescription.main) { + // Treat the extension as being empty => NOT AN ERROR CASE + return Promise.resolve(new EmptyExtension(ExtensionActivationTimes.NONE)); + } + + this._extHostLogService.info(`ExtensionService#_doActivateExtension ${extensionDescription.identifier.value} ${JSON.stringify(reason)}`); + + const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup); + return Promise.all([ + this._loadCommonJSModule(extensionDescription.main, activationTimesBuilder), + this._loadExtensionContext(extensionDescription) + ]).then(values => { + return AbstractExtHostExtensionService._callActivate(this._extHostLogService, extensionDescription.identifier, values[0], values[1], activationTimesBuilder); + }); + } + + protected abstract _loadCommonJSModule(modulePath: string, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise; + + private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise { + + const globalState = new ExtensionMemento(extensionDescription.identifier.value, true, this._storage); + const workspaceState = new ExtensionMemento(extensionDescription.identifier.value, false, this._storage); + + this._extHostLogService.trace(`ExtensionService#loadExtensionContext ${extensionDescription.identifier.value}`); + return Promise.all([ + globalState.whenReady, + workspaceState.whenReady, + this._storagePath.whenReady + ]).then(() => { + const that = this; + return Object.freeze({ + globalState, + workspaceState, + subscriptions: [], + get extensionPath() { return extensionDescription.extensionLocation.fsPath; }, + storagePath: this._storagePath.workspaceValue(extensionDescription), + globalStoragePath: this._storagePath.globalValue(extensionDescription), + asAbsolutePath: (relativePath: string) => { return path.join(extensionDescription.extensionLocation.fsPath, relativePath); }, + logPath: that._extHostLogService.getLogDirectory(extensionDescription.identifier), + executionContext: this._initData.remote.isRemote ? ExtensionExecutionContext.Remote : ExtensionExecutionContext.Local, + }); + }); + } + + private static _callActivate(logService: ILogService, extensionId: ExtensionIdentifier, extensionModule: IExtensionModule, context: IExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { + // Make sure the extension's surface is not undefined + extensionModule = extensionModule || { + activate: undefined, + deactivate: undefined + }; + + return this._callActivateOptional(logService, extensionId, extensionModule, context, activationTimesBuilder).then((extensionExports) => { + return new ActivatedExtension(false, null, activationTimesBuilder.build(), extensionModule, extensionExports, context.subscriptions); + }); + } + + private static _callActivateOptional(logService: ILogService, extensionId: ExtensionIdentifier, extensionModule: IExtensionModule, context: IExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { + if (typeof extensionModule.activate === 'function') { + try { + activationTimesBuilder.activateCallStart(); + logService.trace(`ExtensionService#_callActivateOptional ${extensionId.value}`); + const activateResult: Promise = extensionModule.activate.apply(global, [context]); + activationTimesBuilder.activateCallStop(); + + activationTimesBuilder.activateResolveStart(); + return Promise.resolve(activateResult).then((value) => { + activationTimesBuilder.activateResolveStop(); + return value; + }); + } catch (err) { + return Promise.reject(err); + } + } else { + // No activate found => the module is the extension's exports + return Promise.resolve(extensionModule); + } + } + + // -- eager activation + + // Handle "eager" activation extensions + private _handleEagerExtensions(): Promise { + this._activateByEvent('*', true).then(undefined, (err) => { + console.error(err); + }); + + this._disposables.add(this._extHostWorkspace.onDidChangeWorkspace((e) => this._handleWorkspaceContainsEagerExtensions(e.added))); + const folders = this._extHostWorkspace.workspace ? this._extHostWorkspace.workspace.folders : []; + return this._handleWorkspaceContainsEagerExtensions(folders); + } + + private _handleWorkspaceContainsEagerExtensions(folders: ReadonlyArray): Promise { + if (folders.length === 0) { + return Promise.resolve(undefined); + } + + return Promise.all( + this._registry.getAllExtensionDescriptions().map((desc) => { + return this._handleWorkspaceContainsEagerExtension(folders, desc); + }) + ).then(() => { }); + } + + private _handleWorkspaceContainsEagerExtension(folders: ReadonlyArray, desc: IExtensionDescription): Promise { + const activationEvents = desc.activationEvents; + if (!activationEvents) { + return Promise.resolve(undefined); + } + + if (this.isActivated(desc.identifier)) { + return Promise.resolve(undefined); + } + + const fileNames: string[] = []; + const globPatterns: string[] = []; + + for (const activationEvent of activationEvents) { + if (/^workspaceContains:/.test(activationEvent)) { + const fileNameOrGlob = activationEvent.substr('workspaceContains:'.length); + if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0) { + globPatterns.push(fileNameOrGlob); + } else { + fileNames.push(fileNameOrGlob); + } + } + } + + if (fileNames.length === 0 && globPatterns.length === 0) { + return Promise.resolve(undefined); + } + + const fileNamePromise = Promise.all(fileNames.map((fileName) => this._activateIfFileName(folders, desc.identifier, fileName))).then(() => { }); + const globPatternPromise = this._activateIfGlobPatterns(folders, desc.identifier, globPatterns); + + return Promise.all([fileNamePromise, globPatternPromise]).then(() => { }); + } + + private async _activateIfFileName(folders: ReadonlyArray, extensionId: ExtensionIdentifier, fileName: string): Promise { + + // find exact path + for (const { uri } of folders) { + if (await this._hostUtils.exists(path.join(URI.revive(uri).fsPath, fileName))) { + // the file was found + return ( + this._activateById(extensionId, new ExtensionActivatedByEvent(true, `workspaceContains:${fileName}`)) + .then(undefined, err => console.error(err)) + ); + } + } + + return undefined; + } + + private async _activateIfGlobPatterns(folders: ReadonlyArray, extensionId: ExtensionIdentifier, globPatterns: string[]): Promise { + this._extHostLogService.trace(`extensionHostMain#activateIfGlobPatterns: fileSearch, extension: ${extensionId.value}, entryPoint: workspaceContains`); + + if (globPatterns.length === 0) { + return Promise.resolve(undefined); + } + + const tokenSource = new CancellationTokenSource(); + const searchP = this._mainThreadWorkspaceProxy.$checkExists(folders.map(folder => folder.uri), globPatterns, tokenSource.token); + + const timer = setTimeout(async () => { + tokenSource.cancel(); + this._activateById(extensionId, new ExtensionActivatedByEvent(true, `workspaceContainsTimeout:${globPatterns.join(',')}`)) + .then(undefined, err => console.error(err)); + }, AbstractExtHostExtensionService.WORKSPACE_CONTAINS_TIMEOUT); + + let exists: boolean = false; + try { + exists = await searchP; + } catch (err) { + if (!errors.isPromiseCanceledError(err)) { + console.error(err); + } + } + + tokenSource.dispose(); + clearTimeout(timer); + + if (exists) { + // a file was found matching one of the glob patterns + return ( + this._activateById(extensionId, new ExtensionActivatedByEvent(true, `workspaceContains:${globPatterns.join(',')}`)) + .then(undefined, err => console.error(err)) + ); + } + + return Promise.resolve(undefined); + } + + private _handleExtensionTests(): Promise { + return this._doHandleExtensionTests().then(undefined, error => { + console.error(error); // ensure any error message makes it onto the console + + return Promise.reject(error); + }); + } + + private async _doHandleExtensionTests(): Promise { + const { extensionDevelopmentLocationURI: extensionDevelopmentLocationURI, extensionTestsLocationURI } = this._initData.environment; + if (!(extensionDevelopmentLocationURI && extensionTestsLocationURI && extensionTestsLocationURI.scheme === Schemas.file)) { + return Promise.resolve(undefined); + } + + const extensionTestsPath = originalFSPath(extensionTestsLocationURI); + + // Require the test runner via node require from the provided path + let testRunner: ITestRunner | INewTestRunner | undefined; + let requireError: Error | undefined; + try { + testRunner = await this._loadCommonJSModule(extensionTestsPath, new ExtensionActivationTimesBuilder(false)); + } catch (error) { + requireError = error; + } + + // Execute the runner if it follows the old `run` spec + if (testRunner && typeof testRunner.run === 'function') { + return new Promise((c, e) => { + const oldTestRunnerCallback = (error: Error, failures: number | undefined) => { + if (error) { + e(error.toString()); + } else { + c(undefined); + } + + // after tests have run, we shutdown the host + this._gracefulExit(error || (typeof failures === 'number' && failures > 0) ? 1 /* ERROR */ : 0 /* OK */); + }; + + const runResult = testRunner!.run(extensionTestsPath, oldTestRunnerCallback); + + // Using the new API `run(): Promise` + if (runResult && runResult.then) { + runResult + .then(() => { + c(); + this._gracefulExit(0); + }) + .catch((err: Error) => { + e(err.toString()); + this._gracefulExit(1); + }); + } + }); + } + + // Otherwise make sure to shutdown anyway even in case of an error + else { + this._gracefulExit(1 /* ERROR */); + } + + return Promise.reject(new Error(requireError ? requireError.toString() : nls.localize('extensionTestError', "Path {0} does not point to a valid extension test runner.", extensionTestsPath))); + } + + private _gracefulExit(code: number): void { + // to give the PH process a chance to flush any outstanding console + // messages to the main process, we delay the exit() by some time + setTimeout(() => { + // If extension tests are running, give the exit code to the renderer + if (this._initData.remote.isRemote && !!this._initData.environment.extensionTestsLocationURI) { + this._mainThreadExtensionsProxy.$onExtensionHostExit(code); + return; + } + + this._hostUtils.exit(code); + }, 500); + } + + private _startExtensionHost(): Promise { + if (this._started) { + throw new Error(`Extension host is already started!`); + } + this._started = true; + + return this._readyToStartExtensionHost.wait() + .then(() => this._readyToRunExtensions.open()) + .then(() => this._handleEagerExtensions()) + .then(() => this._handleExtensionTests()) + .then(() => { + this._extHostLogService.info(`eager extensions activated`); + }); + } + + // -- called by extensions + + public registerRemoteAuthorityResolver(authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver): vscode.Disposable { + this._resolvers[authorityPrefix] = resolver; + return toDisposable(() => { + delete this._resolvers[authorityPrefix]; + }); + } + + // -- called by main thread + + public async $resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise { + const authorityPlusIndex = remoteAuthority.indexOf('+'); + if (authorityPlusIndex === -1) { + throw new Error(`Not an authority that can be resolved!`); + } + const authorityPrefix = remoteAuthority.substr(0, authorityPlusIndex); + + await this._almostReadyToRunExtensions.wait(); + await this._activateByEvent(`onResolveRemoteAuthority:${authorityPrefix}`, false); + + const resolver = this._resolvers[authorityPrefix]; + if (!resolver) { + throw new Error(`No remote extension installed to resolve ${authorityPrefix}.`); + } + + try { + const result = await resolver.resolve(remoteAuthority, { resolveAttempt }); + + // Split merged API result into separate authority/options + const authority: ResolvedAuthority = { + authority: remoteAuthority, + host: result.host, + port: result.port + }; + const options: ResolvedOptions = { + extensionHostEnv: result.extensionHostEnv + }; + + return { + type: 'ok', + value: { + authority, + options + } + }; + } catch (err) { + if (err instanceof RemoteAuthorityResolverError) { + return { + type: 'error', + error: { + code: err._code, + message: err._message, + detail: err._detail + } + }; + } + throw err; + } + } + + public $startExtensionHost(enabledExtensionIds: ExtensionIdentifier[]): Promise { + this._registry.keepOnly(enabledExtensionIds); + return this._startExtensionHost(); + } + + public $activateByEvent(activationEvent: string): Promise { + return ( + this._readyToRunExtensions.wait() + .then(_ => this._activateByEvent(activationEvent, false)) + ); + } + + public async $activate(extensionId: ExtensionIdentifier, activationEvent: string): Promise { + await this._readyToRunExtensions.wait(); + if (!this._registry.getExtensionDescription(extensionId)) { + // unknown extension => ignore + return false; + } + await this._activateById(extensionId, new ExtensionActivatedByEvent(false, activationEvent)); + return true; + } + + public async $deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise { + toAdd.forEach((extension) => (extension).extensionLocation = URI.revive(extension.extensionLocation)); + + const trie = await this.getExtensionPathIndex(); + + await Promise.all(toRemove.map(async (extensionId) => { + const extensionDescription = this._registry.getExtensionDescription(extensionId); + if (!extensionDescription) { + return; + } + const realpathValue = await this._hostUtils.realpath(extensionDescription.extensionLocation.fsPath); + trie.delete(URI.file(realpathValue).fsPath); + })); + + await Promise.all(toAdd.map(async (extensionDescription) => { + const realpathValue = await this._hostUtils.realpath(extensionDescription.extensionLocation.fsPath); + trie.set(URI.file(realpathValue).fsPath, extensionDescription); + })); + + this._registry.deltaExtensions(toAdd, toRemove); + return Promise.resolve(undefined); + } + + public async $test_latency(n: number): Promise { + return n; + } + + public async $test_up(b: VSBuffer): Promise { + return b.byteLength; + } + + public async $test_down(size: number): Promise { + let buff = VSBuffer.alloc(size); + let value = Math.random() % 256; + for (let i = 0; i < size; i++) { + buff.writeUInt8(value, i); + } + return buff; + } + + public abstract async $setRemoteEnvironment(env: { [key: string]: string | null }): Promise; +} + + +type TelemetryActivationEvent = { + id: string; + name: string; + extensionVersion: string; + publisherDisplayName: string; + activationEvents: string | null; + isBuiltin: boolean; + reason: string; +}; + +function getTelemetryActivationEvent(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): TelemetryActivationEvent { + const reasonStr = reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : + reason instanceof ExtensionActivatedByAPI ? 'api' : + ''; + const event = { + id: extensionDescription.identifier.value, + name: extensionDescription.name, + extensionVersion: extensionDescription.version, + publisherDisplayName: extensionDescription.publisher, + activationEvents: extensionDescription.activationEvents ? extensionDescription.activationEvents.join(',') : null, + isBuiltin: extensionDescription.isBuiltin, + reason: reasonStr + }; + + return event; +} + + +export const IExtHostExtensionService = createDecorator('IExtHostExtensionService'); + +export interface IExtHostExtensionService extends AbstractExtHostExtensionService { + _serviceBrand: any; + initialize(): Promise; + isActivated(extensionId: ExtensionIdentifier): boolean; + activateByIdWithErrors(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise; + deactivateAll(): Promise; + getExtensionExports(extensionId: ExtensionIdentifier): IExtensionAPI | null | undefined; + getExtensionRegistry(): Promise; + getExtensionPathIndex(): Promise>; + registerRemoteAuthorityResolver(authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver): vscode.Disposable; +} diff --git a/src/vs/workbench/api/common/extHostFileSystem.ts b/src/vs/workbench/api/common/extHostFileSystem.ts index 9295d0f23a..fc1908ee2f 100644 --- a/src/vs/workbench/api/common/extHostFileSystem.ts +++ b/src/vs/workbench/api/common/extHostFileSystem.ts @@ -155,7 +155,7 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { private readonly _usedSchemes = new Set(); private readonly _watches = new Map(); - private _linkProviderRegistration: IDisposable; + private _linkProviderRegistration?: IDisposable; private _handlePool: number = 0; readonly fileSystem: vscode.FileSystem; diff --git a/src/vs/workbench/api/common/extHostFileSystemEventService.ts b/src/vs/workbench/api/common/extHostFileSystemEventService.ts index 94e7dcb1f6..d295d969dd 100644 --- a/src/vs/workbench/api/common/extHostFileSystemEventService.ts +++ b/src/vs/workbench/api/common/extHostFileSystemEventService.ts @@ -9,7 +9,7 @@ import { IRelativePattern, parse } from 'vs/base/common/glob'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import * as vscode from 'vscode'; -import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, ResourceFileEditDto, ResourceTextEditDto, MainThreadTextEditorsShape } from './extHost.protocol'; +import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, IResourceFileEditDto, IResourceTextEditDto, MainThreadTextEditorsShape } from './extHost.protocol'; import * as typeConverter from './extHostTypeConverters'; import { Disposable, WorkspaceEdit } from './extHostTypes'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -169,7 +169,7 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ } // flatten all WorkspaceEdits collected via waitUntil-call // and apply them in one go. - const allEdits = new Array>(); + const allEdits = new Array>(); for (let edit of edits) { if (edit) { // sparse array let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors); diff --git a/src/vs/workbench/api/common/extHostInitDataService.ts b/src/vs/workbench/api/common/extHostInitDataService.ts new file mode 100644 index 0000000000..9142cba298 --- /dev/null +++ b/src/vs/workbench/api/common/extHostInitDataService.ts @@ -0,0 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IInitData } from './extHost.protocol'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IExtHostInitDataService = createDecorator('IExtHostInitDataService'); + +export interface IExtHostInitDataService extends Readonly { + _serviceBrand: undefined; +} + diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 4aeb852ab6..f95f582e03 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -14,7 +14,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics'; import { asPromise } from 'vs/base/common/async'; -import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, WorkspaceSymbolDto, SuggestResultDto, WorkspaceSymbolsDto, CodeActionDto, ISerializedDocumentFilter, WorkspaceEditDto, ISerializedSignatureHelpProviderMetadata, LinkDto, CodeLensDto, SuggestDataDto, LinksListDto, ChainedCacheId, CodeLensListDto, CodeActionListDto, SignatureHelpDto, SignatureHelpContextDto } from './extHost.protocol'; +import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, IRawColorInfo, IMainContext, IdObject, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILanguageConfigurationDto, IWorkspaceSymbolDto, ISuggestResultDto, IWorkspaceSymbolsDto, ICodeActionDto, IDocumentFilterDto, IWorkspaceEditDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICodeLensDto, ISuggestDataDto, ILinksListDto, ChainedCacheId, ICodeLensListDto, ICodeActionListDto, ISignatureHelpDto, ISignatureHelpContextDto } from './extHost.protocol'; import { regExpLeadsToEndlessLoop, regExpFlags } from 'vs/base/common/strings'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange, Range as EditorRange } from 'vs/editor/common/core/range'; @@ -111,7 +111,7 @@ class CodeLensAdapter { private readonly _provider: vscode.CodeLensProvider ) { } - provideCodeLenses(resource: URI, token: CancellationToken): Promise { + provideCodeLenses(resource: URI, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); return asPromise(() => this._provider.provideCodeLenses(doc, token)).then(lenses => { @@ -124,7 +124,7 @@ class CodeLensAdapter { const disposables = new DisposableStore(); this._disposables.set(cacheId, disposables); - const result: CodeLensListDto = { + const result: ICodeLensListDto = { cacheId, lenses: [], }; @@ -141,7 +141,7 @@ class CodeLensAdapter { }); } - resolveCodeLens(symbol: CodeLensDto, token: CancellationToken): Promise { + resolveCodeLens(symbol: ICodeLensDto, token: CancellationToken): Promise { const lens = symbol.cacheId && this._cache.get(...symbol.cacheId); if (!lens) { @@ -308,7 +308,7 @@ class ReferenceAdapter { } } -export interface CustomCodeAction extends CodeActionDto { +export interface CustomCodeAction extends ICodeActionDto { _isSynthetic?: boolean; } @@ -327,7 +327,7 @@ class CodeActionAdapter { private readonly _extensionId: ExtensionIdentifier ) { } - provideCodeActions(resource: URI, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise { + provideCodeActions(resource: URI, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); const ran = Selection.isISelection(rangeOrSelection) @@ -391,7 +391,7 @@ class CodeActionAdapter { } } - return { cacheId, actions }; + return { cacheId, actions }; }); } @@ -480,8 +480,8 @@ class NavigateTypeAdapter { this._provider = provider; } - provideWorkspaceSymbols(search: string, token: CancellationToken): Promise { - const result: WorkspaceSymbolsDto = IdObject.mixin({ symbols: [] }); + provideWorkspaceSymbols(search: string, token: CancellationToken): Promise { + const result: IWorkspaceSymbolsDto = IdObject.mixin({ symbols: [] }); return asPromise(() => this._provider.provideWorkspaceSymbols(search, token)).then(value => { if (isNonEmptyArray(value)) { for (const item of value) { @@ -506,7 +506,7 @@ class NavigateTypeAdapter { }); } - resolveWorkspaceSymbol(symbol: WorkspaceSymbolDto, token: CancellationToken): Promise { + resolveWorkspaceSymbol(symbol: IWorkspaceSymbolDto, token: CancellationToken): Promise { if (typeof this._provider.resolveWorkspaceSymbol !== 'function') { return Promise.resolve(symbol); @@ -543,7 +543,7 @@ class RenameAdapter { private readonly _provider: vscode.RenameProvider ) { } - provideRenameEdits(resource: URI, position: IPosition, newName: string, token: CancellationToken): Promise { + provideRenameEdits(resource: URI, position: IPosition, newName: string, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); const pos = typeConvert.Position.to(position); @@ -556,10 +556,10 @@ class RenameAdapter { }, err => { const rejectReason = RenameAdapter._asMessage(err); if (rejectReason) { - return { rejectReason, edits: undefined! }; + return { rejectReason, edits: undefined! }; } else { // generic error - return Promise.reject(err); + return Promise.reject(err); } }); } @@ -633,7 +633,7 @@ class SuggestAdapter { this._provider = provider; } - provideCompletionItems(resource: URI, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise { + provideCompletionItems(resource: URI, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); const pos = typeConvert.Position.to(position); @@ -662,7 +662,7 @@ class SuggestAdapter { const wordRangeBeforePos = (doc.getWordRangeAtPosition(pos) as Range || new Range(pos, pos)) .with({ end: pos }); - const result: SuggestResultDto = { + const result: ISuggestResultDto = { x: pid, b: [], a: typeConvert.Range.from(wordRangeBeforePos), @@ -682,7 +682,7 @@ class SuggestAdapter { }); } - resolveCompletionItem(_resource: URI, position: IPosition, id: ChainedCacheId, token: CancellationToken): Promise { + resolveCompletionItem(_resource: URI, position: IPosition, id: ChainedCacheId, token: CancellationToken): Promise { if (typeof this._provider.resolveCompletionItem !== 'function') { return Promise.resolve(undefined); @@ -710,7 +710,7 @@ class SuggestAdapter { this._cache.delete(id); } - private _convertCompletionItem(item: vscode.CompletionItem, position: vscode.Position, id: ChainedCacheId): SuggestDataDto | undefined { + private _convertCompletionItem(item: vscode.CompletionItem, position: vscode.Position, id: ChainedCacheId): ISuggestDataDto | undefined { if (typeof item.label !== 'string' || item.label.length === 0) { console.warn('INVALID text edit -> must have at least a label'); return undefined; @@ -721,7 +721,7 @@ class SuggestAdapter { throw Error('DisposableStore is missing...'); } - const result: SuggestDataDto = { + const result: ISuggestDataDto = { // x: id, // @@ -777,7 +777,7 @@ class SignatureHelpAdapter { private readonly _provider: vscode.SignatureHelpProvider, ) { } - provideSignatureHelp(resource: URI, position: IPosition, context: SignatureHelpContextDto, token: CancellationToken): Promise { + provideSignatureHelp(resource: URI, position: IPosition, context: ISignatureHelpContextDto, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); const pos = typeConvert.Position.to(position); const vscodeContext = this.reviveContext(context); @@ -791,7 +791,7 @@ class SignatureHelpAdapter { }); } - private reviveContext(context: SignatureHelpContextDto): vscode.SignatureHelpContext { + private reviveContext(context: ISignatureHelpContextDto): vscode.SignatureHelpContext { let activeSignatureHelp: vscode.SignatureHelp | undefined = undefined; if (context.activeSignatureHelp) { const revivedSignatureHelp = typeConvert.SignatureHelp.to(context.activeSignatureHelp); @@ -855,7 +855,7 @@ class LinkProviderAdapter { private readonly _provider: vscode.DocumentLinkProvider ) { } - provideLinks(resource: URI, token: CancellationToken): Promise { + provideLinks(resource: URI, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); return asPromise(() => this._provider.provideDocumentLinks(doc, token)).then(links => { @@ -877,9 +877,9 @@ class LinkProviderAdapter { } else { // cache links for future resolving const pid = this._cache.add(links); - const result: LinksListDto = { links: [], id: pid }; + const result: ILinksListDto = { links: [], id: pid }; for (let i = 0; i < links.length; i++) { - const dto: LinkDto = typeConvert.DocumentLink.from(links[i]); + const dto: ILinkDto = typeConvert.DocumentLink.from(links[i]); dto.cacheId = [pid, i]; result.links.push(dto); } @@ -888,7 +888,7 @@ class LinkProviderAdapter { }); } - resolveLink(id: ChainedCacheId, token: CancellationToken): Promise { + resolveLink(id: ChainedCacheId, token: CancellationToken): Promise { if (typeof this._provider.resolveDocumentLink !== 'function') { return Promise.resolve(undefined); } @@ -1103,11 +1103,11 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { this._logService = logService; } - private _transformDocumentSelector(selector: vscode.DocumentSelector): Array { + private _transformDocumentSelector(selector: vscode.DocumentSelector): Array { return coalesce(asArray(selector).map(sel => this._doTransformDocumentSelector(sel))); } - private _doTransformDocumentSelector(selector: string | vscode.DocumentFilter): ISerializedDocumentFilter | undefined { + private _doTransformDocumentSelector(selector: string | vscode.DocumentFilter): IDocumentFilterDto | undefined { if (typeof selector === 'string') { return { $serialized: true, @@ -1215,11 +1215,11 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return result; } - $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise { + $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise { return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.provideCodeLenses(URI.revive(resource), token), undefined); } - $resolveCodeLens(handle: number, symbol: CodeLensDto, token: CancellationToken): Promise { + $resolveCodeLens(handle: number, symbol: ICodeLensDto, token: CancellationToken): Promise { return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.resolveCodeLens(symbol, token), undefined); } @@ -1314,7 +1314,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { } - $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise { + $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise { return this._withAdapter(handle, CodeActionAdapter, adapter => adapter.provideCodeActions(URI.revive(resource), rangeOrSelection, context, token), undefined); } @@ -1362,11 +1362,11 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._createDisposable(handle); } - $provideWorkspaceSymbols(handle: number, search: string, token: CancellationToken): Promise { + $provideWorkspaceSymbols(handle: number, search: string, token: CancellationToken): Promise { return this._withAdapter(handle, NavigateTypeAdapter, adapter => adapter.provideWorkspaceSymbols(search, token), { symbols: [] }); } - $resolveWorkspaceSymbol(handle: number, symbol: WorkspaceSymbolDto, token: CancellationToken): Promise { + $resolveWorkspaceSymbol(handle: number, symbol: IWorkspaceSymbolDto, token: CancellationToken): Promise { return this._withAdapter(handle, NavigateTypeAdapter, adapter => adapter.resolveWorkspaceSymbol(symbol, token), undefined); } @@ -1382,7 +1382,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._createDisposable(handle); } - $provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string, token: CancellationToken): Promise { + $provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string, token: CancellationToken): Promise { return this._withAdapter(handle, RenameAdapter, adapter => adapter.provideRenameEdits(URI.revive(resource), position, newName, token), undefined); } @@ -1398,11 +1398,11 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._createDisposable(handle); } - $provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise { + $provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise { return this._withAdapter(handle, SuggestAdapter, adapter => adapter.provideCompletionItems(URI.revive(resource), position, context, token), undefined); } - $resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, id: ChainedCacheId, token: CancellationToken): Promise { + $resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, id: ChainedCacheId, token: CancellationToken): Promise { return this._withAdapter(handle, SuggestAdapter, adapter => adapter.resolveCompletionItem(URI.revive(resource), position, id, token), undefined); } @@ -1413,7 +1413,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { // --- parameter hints registerSignatureHelpProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.SignatureHelpProvider, metadataOrTriggerChars: string[] | vscode.SignatureHelpProviderMetadata): vscode.Disposable { - const metadata: ISerializedSignatureHelpProviderMetadata | undefined = Array.isArray(metadataOrTriggerChars) + const metadata: ISignatureHelpProviderMetadataDto | undefined = Array.isArray(metadataOrTriggerChars) ? { triggerCharacters: metadataOrTriggerChars, retriggerCharacters: [] } : metadataOrTriggerChars; @@ -1422,7 +1422,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._createDisposable(handle); } - $provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: SignatureHelpContextDto, token: CancellationToken): Promise { + $provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: ISignatureHelpContextDto, token: CancellationToken): Promise { return this._withAdapter(handle, SignatureHelpAdapter, adapter => adapter.provideSignatureHelp(URI.revive(resource), position, context, token), undefined); } @@ -1438,11 +1438,11 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._createDisposable(handle); } - $provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise { + $provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise { return this._withAdapter(handle, LinkProviderAdapter, adapter => adapter.provideLinks(URI.revive(resource), token), undefined); } - $resolveDocumentLink(handle: number, id: ChainedCacheId, token: CancellationToken): Promise { + $resolveDocumentLink(handle: number, id: ChainedCacheId, token: CancellationToken): Promise { return this._withAdapter(handle, LinkProviderAdapter, adapter => adapter.resolveLink(id, token), undefined); } @@ -1504,14 +1504,14 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { // --- configuration - private static _serializeRegExp(regExp: RegExp): ISerializedRegExp { + private static _serializeRegExp(regExp: RegExp): IRegExpDto { return { pattern: regExp.source, flags: regExpFlags(regExp), }; } - private static _serializeIndentationRule(indentationRule: vscode.IndentationRule): ISerializedIndentationRule { + private static _serializeIndentationRule(indentationRule: vscode.IndentationRule): IIndentationRuleDto { return { decreaseIndentPattern: ExtHostLanguageFeatures._serializeRegExp(indentationRule.decreaseIndentPattern), increaseIndentPattern: ExtHostLanguageFeatures._serializeRegExp(indentationRule.increaseIndentPattern), @@ -1520,7 +1520,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { }; } - private static _serializeOnEnterRule(onEnterRule: vscode.OnEnterRule): ISerializedOnEnterRule { + private static _serializeOnEnterRule(onEnterRule: vscode.OnEnterRule): IOnEnterRuleDto { return { beforeText: ExtHostLanguageFeatures._serializeRegExp(onEnterRule.beforeText), afterText: onEnterRule.afterText ? ExtHostLanguageFeatures._serializeRegExp(onEnterRule.afterText) : undefined, @@ -1529,7 +1529,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { }; } - private static _serializeOnEnterRules(onEnterRules: vscode.OnEnterRule[]): ISerializedOnEnterRule[] { + private static _serializeOnEnterRules(onEnterRules: vscode.OnEnterRule[]): IOnEnterRuleDto[] { return onEnterRules.map(ExtHostLanguageFeatures._serializeOnEnterRule); } @@ -1549,7 +1549,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { } const handle = this._nextHandle(); - const serializedConfiguration: ISerializedLanguageConfiguration = { + const serializedConfiguration: ILanguageConfigurationDto = { comments: configuration.comments, brackets: configuration.brackets, wordPattern: configuration.wordPattern ? ExtHostLanguageFeatures._serializeRegExp(configuration.wordPattern) : undefined, diff --git a/src/vs/workbench/api/common/extHostMemento.ts b/src/vs/workbench/api/common/extHostMemento.ts index ab2b95128f..ffcddef87f 100644 --- a/src/vs/workbench/api/common/extHostMemento.ts +++ b/src/vs/workbench/api/common/extHostMemento.ts @@ -14,7 +14,7 @@ export class ExtensionMemento implements IExtensionMemento { private readonly _storage: ExtHostStorage; private readonly _init: Promise; - private _value: { [n: string]: any; }; + private _value?: { [n: string]: any; }; private readonly _storageListener: IDisposable; constructor(id: string, global: boolean, storage: ExtHostStorage) { @@ -41,7 +41,7 @@ export class ExtensionMemento implements IExtensionMemento { get(key: string): T | undefined; get(key: string, defaultValue: T): T; get(key: string, defaultValue?: T): T { - let value = this._value[key]; + let value = this._value![key]; if (typeof value === 'undefined') { value = defaultValue; } @@ -49,8 +49,8 @@ export class ExtensionMemento implements IExtensionMemento { } update(key: string, value: any): Promise { - this._value[key] = value; - return this._storage.setValue(this._shared, this._id, this._value); + this._value![key] = value; + return this._storage.setValue(this._shared, this._id, this._value!); } dispose(): void { diff --git a/src/vs/workbench/api/common/extHostOutput.ts b/src/vs/workbench/api/common/extHostOutput.ts index bf8d881743..02bc7b4b1a 100644 --- a/src/vs/workbench/api/common/extHostOutput.ts +++ b/src/vs/workbench/api/common/extHostOutput.ts @@ -3,12 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { MainContext, MainThreadOutputServiceShape, IMainContext, ExtHostOutputServiceShape } from './extHost.protocol'; +import { MainContext, MainThreadOutputServiceShape, ExtHostOutputServiceShape } from './extHost.protocol'; import * as vscode from 'vscode'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; -import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { VSBuffer } from 'vs/base/common/buffer'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; export abstract class AbstractExtHostOutputChannel extends Disposable implements vscode.OutputChannel { @@ -27,6 +29,7 @@ export abstract class AbstractExtHostOutputChannel extends Disposable implements this._name = name; this._proxy = proxy; this._id = proxy.$register(this.name, log, file); + this._disposed = false; this._offset = 0; } @@ -105,38 +108,50 @@ class ExtHostLogFileOutputChannel extends AbstractExtHostOutputChannel { } } -export interface IOutputChannelFactory { - createOutputChannel(name: string, logsLocation: URI, proxy: MainThreadOutputServiceShape): Promise; -} +export class LazyOutputChannel implements vscode.OutputChannel { -export const PushOutputChannelFactory = new class implements IOutputChannelFactory { - async createOutputChannel(name: string, _logsLocation: URI, proxy: MainThreadOutputServiceShape): Promise { - return new ExtHostPushOutputChannel(name, proxy); + constructor( + readonly name: string, + private readonly _channel: Promise + ) { } + + append(value: string): void { + this._channel.then(channel => channel.append(value)); } -}; + appendLine(value: string): void { + this._channel.then(channel => channel.appendLine(value)); + } + clear(): void { + this._channel.then(channel => channel.clear()); + } + show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void { + this._channel.then(channel => channel.show(columnOrPreserveFocus, preserveFocus)); + } + hide(): void { + this._channel.then(channel => channel.hide()); + } + dispose(): void { + this._channel.then(channel => channel.dispose()); + } +} export class ExtHostOutputService implements ExtHostOutputServiceShape { - private readonly _factory: IOutputChannelFactory; - private readonly _logsLocation: URI; - private readonly _proxy: MainThreadOutputServiceShape; - private readonly _channels: Map = new Map(); - private _visibleChannelDisposable: IDisposable; + readonly _serviceBrand: any; - constructor(factory: IOutputChannelFactory, logsLocation: URI, mainContext: IMainContext) { - this._factory = factory; - this._logsLocation = logsLocation; - this._proxy = mainContext.getProxy(MainContext.MainThreadOutputService); + protected readonly _proxy: MainThreadOutputServiceShape; + protected readonly _channels: Map = new Map(); + protected readonly _visibleChannelDisposable = new MutableDisposable(); + + constructor(@IExtHostRpcService extHostRpc: IExtHostRpcService) { + this._proxy = extHostRpc.getProxy(MainContext.MainThreadOutputService); } $setVisibleChannel(channelId: string): void { - if (this._visibleChannelDisposable) { - this._visibleChannelDisposable = dispose(this._visibleChannelDisposable); - } if (channelId) { const channel = this._channels.get(channelId); if (channel) { - this._visibleChannelDisposable = channel.onDidAppend(() => channel.update()); + this._visibleChannelDisposable.value = channel.onDidAppend(() => channel.update()); } } } @@ -145,33 +160,8 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { name = name.trim(); if (!name) { throw new Error('illegal argument `name`. must not be falsy'); - } else { - const extHostOutputChannel = this._factory.createOutputChannel(name, this._logsLocation, this._proxy); - extHostOutputChannel.then(channel => channel._id.then(id => this._channels.set(id, channel))); - return { - get name(): string { - return name; - }, - append(value: string): void { - extHostOutputChannel.then(channel => channel.append(value)); - }, - appendLine(value: string): void { - extHostOutputChannel.then(channel => channel.appendLine(value)); - }, - clear(): void { - extHostOutputChannel.then(channel => channel.clear()); - }, - show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void { - extHostOutputChannel.then(channel => channel.show(columnOrPreserveFocus, preserveFocus)); - }, - hide(): void { - extHostOutputChannel.then(channel => channel.hide()); - }, - dispose(): void { - extHostOutputChannel.then(channel => channel.dispose()); - } - }; } + return new ExtHostPushOutputChannel(name, this._proxy); } createOutputChannelFromLogFile(name: string, file: URI): vscode.OutputChannel { @@ -185,3 +175,6 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { return new ExtHostLogFileOutputChannel(name, file, this._proxy); } } + +export interface IExtHostOutputService extends ExtHostOutputService { } +export const IExtHostOutputService = createDecorator('IExtHostOutputService'); diff --git a/src/vs/workbench/api/common/extHostRpcService.ts b/src/vs/workbench/api/common/extHostRpcService.ts new file mode 100644 index 0000000000..6e4d351083 --- /dev/null +++ b/src/vs/workbench/api/common/extHostRpcService.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ProxyIdentifier, IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IExtHostRpcService = createDecorator('IExtHostRpcService'); + +export interface IExtHostRpcService extends IRPCProtocol { + _serviceBrand: any; +} + +export class ExtHostRpcService implements IExtHostRpcService { + readonly _serviceBrand: any; + + readonly getProxy: (identifier: ProxyIdentifier) => T; + readonly set: (identifier: ProxyIdentifier, instance: R) => R; + readonly assertRegistered: (identifiers: ProxyIdentifier[]) => void; + + constructor(rpcProtocol: IRPCProtocol) { + this.getProxy = rpcProtocol.getProxy.bind(rpcProtocol); + this.set = rpcProtocol.set.bind(rpcProtocol); + this.assertRegistered = rpcProtocol.assertRegistered.bind(rpcProtocol); + + } + +} diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 86451374b4..e2ec5628f7 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -9,7 +9,7 @@ import { debounce } from 'vs/base/common/decorators'; import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { asPromise } from 'vs/base/common/async'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; -import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, CommandDto } from './extHost.protocol'; +import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto } from './extHost.protocol'; import { sortedDiff } from 'vs/base/common/arrays'; import { comparePaths } from 'vs/base/common/comparers'; import * as vscode from 'vscode'; @@ -445,7 +445,7 @@ class ExtHostSourceControl implements vscode.SourceControl { this._statusBarCommands = statusBarCommands; - const internal = (statusBarCommands || []).map(c => this._commands.converter.toInternal(c, this._statusBarDisposables.value!)) as CommandDto[]; + const internal = (statusBarCommands || []).map(c => this._commands.converter.toInternal(c, this._statusBarDisposables.value!)) as ICommandDto[]; this._proxy.$updateSourceControl(this.handle, { statusBarCommands: internal }); } diff --git a/src/vs/workbench/api/common/extHostSearch.ts b/src/vs/workbench/api/common/extHostSearch.ts new file mode 100644 index 0000000000..90fd4a5df3 --- /dev/null +++ b/src/vs/workbench/api/common/extHostSearch.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IDisposable } from 'vs/base/common/lifecycle'; +import * as vscode from 'vscode'; +import { ExtHostSearchShape } from '../common/extHost.protocol'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export interface IExtHostSearch extends ExtHostSearchShape { + registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProvider): IDisposable; + registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProvider): IDisposable; +} + +export const IExtHostSearch = createDecorator('IExtHostSearch'); diff --git a/src/vs/workbench/api/common/extHostStorage.ts b/src/vs/workbench/api/common/extHostStorage.ts index 228c70b9b2..8e6911e631 100644 --- a/src/vs/workbench/api/common/extHostStorage.ts +++ b/src/vs/workbench/api/common/extHostStorage.ts @@ -3,8 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { MainContext, MainThreadStorageShape, IMainContext, ExtHostStorageShape } from './extHost.protocol'; +import { MainContext, MainThreadStorageShape, ExtHostStorageShape } from './extHost.protocol'; import { Emitter } from 'vs/base/common/event'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export interface IStorageChangeEvent { shared: boolean; @@ -14,12 +16,14 @@ export interface IStorageChangeEvent { export class ExtHostStorage implements ExtHostStorageShape { + readonly _serviceBrand: any; + private _proxy: MainThreadStorageShape; private _onDidChangeStorage = new Emitter(); readonly onDidChangeStorage = this._onDidChangeStorage.event; - constructor(mainContext: IMainContext) { + constructor(mainContext: IExtHostRpcService) { this._proxy = mainContext.getProxy(MainContext.MainThreadStorage); } @@ -35,3 +39,6 @@ export class ExtHostStorage implements ExtHostStorageShape { this._onDidChangeStorage.fire({ shared, key, value }); } } + +export interface IExtHostStorage extends ExtHostStorage { } +export const IExtHostStorage = createDecorator('IExtHostStorage'); diff --git a/src/vs/workbench/api/common/extHostStoragePaths.ts b/src/vs/workbench/api/common/extHostStoragePaths.ts new file mode 100644 index 0000000000..5fea39d51d --- /dev/null +++ b/src/vs/workbench/api/common/extHostStoragePaths.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IExtensionStoragePaths = createDecorator('IExtensionStoragePaths'); + +export interface IExtensionStoragePaths { + _serviceBrand: any; + whenReady: Promise; + workspaceValue(extension: IExtensionDescription): string | undefined; + globalValue(extension: IExtensionDescription): string; +} diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts new file mode 100644 index 0000000000..0eb550f914 --- /dev/null +++ b/src/vs/workbench/api/common/extHostTask.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { ExtHostTaskShape } from 'vs/workbench/api/common/extHost.protocol'; +import * as vscode from 'vscode'; +import { TaskSystemInfoDTO } from '../common/shared/tasks'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export interface IExtHostTask extends ExtHostTaskShape { + + readonly _serviceBrand: any; + + taskExecutions: vscode.TaskExecution[]; + onDidStartTask: Event; + onDidEndTask: Event; + onDidStartTaskProcess: Event; + onDidEndTaskProcess: Event; + + registerTaskProvider(extension: IExtensionDescription, type: string, provider: vscode.TaskProvider): vscode.Disposable; + registerTaskSystem(scheme: string, info: TaskSystemInfoDTO): void; + fetchTasks(filter?: vscode.TaskFilter): Promise; + executeTask(extension: IExtensionDescription, task: vscode.Task): Promise; + terminateTask(execution: vscode.TaskExecution): Promise; +} + +export const IExtHostTask = createDecorator('IExtHostTask'); diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts new file mode 100644 index 0000000000..b436a5fe5f --- /dev/null +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { Event } from 'vs/base/common/event'; +import { ExtHostTerminalServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export interface IExtHostTerminalService extends ExtHostTerminalServiceShape { + + _serviceBrand: any; + + activeTerminal: vscode.Terminal | undefined; + terminals: vscode.Terminal[]; + + onDidCloseTerminal: Event; + onDidOpenTerminal: Event; + onDidChangeActiveTerminal: Event; + onDidChangeTerminalDimensions: Event; + onDidWriteTerminalData: Event; + + createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal; + createTerminalFromOptions(options: vscode.TerminalOptions): vscode.Terminal; + createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal; + attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): void; + getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string; +} + +export const IExtHostTerminalService = createDecorator('IExtHostTerminalService'); diff --git a/src/vs/workbench/api/common/extHostTextEditor.ts b/src/vs/workbench/api/common/extHostTextEditor.ts index a3a0560684..bce13601a7 100644 --- a/src/vs/workbench/api/common/extHostTextEditor.ts +++ b/src/vs/workbench/api/common/extHostTextEditor.ts @@ -151,11 +151,11 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions { private _proxy: MainThreadTextEditorsShape; private _id: string; - private _tabSize: number; - private _indentSize: number; - private _insertSpaces: boolean; - private _cursorStyle: TextEditorCursorStyle; - private _lineNumbers: TextEditorLineNumbersStyle; + private _tabSize!: number; + private _indentSize!: number; + private _insertSpaces!: boolean; + private _cursorStyle!: TextEditorCursorStyle; + private _lineNumbers!: TextEditorLineNumbersStyle; constructor(proxy: MainThreadTextEditorsShape, id: string, source: IResolvedTextEditorConfiguration) { this._proxy = proxy; diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index b6ac6168ed..9dc99da82c 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -13,7 +13,7 @@ import { ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.proto import { ITreeItem, TreeViewItemHandleArg, ITreeItemLabel, IRevealOptions } from 'vs/workbench/common/views'; import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { asPromise } from 'vs/base/common/async'; -import { TreeItemCollapsibleState, ThemeIcon, MarkdownString } from 'vs/workbench/api/common/extHostTypes'; +import { TreeItemCollapsibleState, ThemeIcon } from 'vs/workbench/api/common/extHostTypes'; import { isUndefinedOrNull, isString } from 'vs/base/common/types'; import { equals, coalesce } from 'vs/base/common/arrays'; import { ILogService } from 'vs/platform/log/common/log'; @@ -84,7 +84,10 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { get visible() { return treeView.visible; }, get onDidChangeVisibility() { return treeView.onDidChangeVisibility; }, get message() { return treeView.message; }, - set message(message: string | MarkdownString) { checkProposedApiEnabled(extension); treeView.message = message; }, + set message(message: string) { + checkProposedApiEnabled(extension); + treeView.message = message; + }, reveal: (element: T, options?: IRevealOptions): Promise => { return treeView.reveal(element, options); }, @@ -182,6 +185,7 @@ export class ExtHostTreeView extends Disposable { private _onDidChangeData: Emitter> = this._register(new Emitter>()); private refreshPromise: Promise = Promise.resolve(); + private refreshQueue: Promise = Promise.resolve(); constructor(private viewId: string, options: vscode.TreeViewOptions, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter, private logService: ILogService, private extension: IExtensionDescription) { super(); @@ -214,9 +218,11 @@ export class ExtHostTreeView extends Disposable { return result; }, 200)(({ message, elements }) => { if (elements.length) { - const _promiseCallback = promiseCallback; - refreshingPromise = null; - this.refresh(elements).then(() => _promiseCallback()); + this.refreshQueue = this.refreshQueue.then(() => { + const _promiseCallback = promiseCallback; + refreshingPromise = null; + return this.refresh(elements).then(() => _promiseCallback()); + }); } if (message) { this.proxy.$setMessage(this.viewId, this._message); @@ -255,12 +261,12 @@ export class ExtHostTreeView extends Disposable { .then(treeNode => this.proxy.$reveal(this.viewId, treeNode.item, parentChain.map(p => p.item), { select, focus, expand })), error => this.logService.error(error)); } - private _message: string | MarkdownString; - get message(): string | MarkdownString { + private _message: string = ''; + get message(): string { return this._message; } - set message(message: string | MarkdownString) { + set message(message: string) { this._message = message; this._onDidChangeData.fire({ message: true, element: false }); } @@ -583,9 +589,9 @@ export class ExtHostTreeView extends Disposable { if (node) { if (node.children) { for (const child of node.children) { - const childEleement = this.elements.get(child.item.handle); - if (childEleement) { - this.clear(childEleement); + const childElement = this.elements.get(child.item.handle); + if (childElement) { + this.clear(childElement); } } } @@ -601,9 +607,9 @@ export class ExtHostTreeView extends Disposable { if (node) { if (node.children) { for (const child of node.children) { - const childEleement = this.elements.get(child.item.handle); - if (childEleement) { - this.clear(childEleement); + const childElement = this.elements.get(child.item.handle); + if (childElement) { + this.clear(childElement); } } } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index c6384916ce..72b54684de 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -19,7 +19,7 @@ import { IRange } from 'vs/editor/common/core/range'; import { ISelection } from 'vs/editor/common/core/selection'; import * as htmlContent from 'vs/base/common/htmlContent'; import * as languageSelector from 'vs/editor/common/modes/languageSelector'; -import { WorkspaceEditDto, ResourceTextEditDto, ResourceFileEditDto } from 'vs/workbench/api/common/extHost.protocol'; +import { IWorkspaceEditDto, IResourceTextEditDto, IResourceFileEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { MarkerSeverity, IRelatedInformation, IMarkerData, MarkerTag } from 'vs/platform/markers/common/markers'; import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; @@ -442,7 +442,7 @@ export namespace TextEdit { export function from(edit: vscode.TextEdit): modes.TextEdit { return { text: edit.newText, - eol: EndOfLine.from(edit.newEol), + eol: edit.newEol && EndOfLine.from(edit.newEol), range: Range.from(edit.range) }; } @@ -455,8 +455,8 @@ export namespace TextEdit { } export namespace WorkspaceEdit { - export function from(value: vscode.WorkspaceEdit, documents?: ExtHostDocumentsAndEditors): WorkspaceEditDto { - const result: WorkspaceEditDto = { + export function from(value: vscode.WorkspaceEdit, documents?: ExtHostDocumentsAndEditors): IWorkspaceEditDto { + const result: IWorkspaceEditDto = { edits: [] }; for (const entry of (value as types.WorkspaceEdit)._allEntries()) { @@ -464,28 +464,28 @@ export namespace WorkspaceEdit { if (Array.isArray(uriOrEdits)) { // text edits const doc = documents && uri ? documents.getDocument(uri) : undefined; - result.edits.push({ resource: uri, modelVersionId: doc && doc.version, edits: uriOrEdits.map(TextEdit.from) }); + result.edits.push({ resource: uri, modelVersionId: doc && doc.version, edits: uriOrEdits.map(TextEdit.from) }); } else { // resource edits - result.edits.push({ oldUri: uri, newUri: uriOrEdits, options: entry[2] }); + result.edits.push({ oldUri: uri, newUri: uriOrEdits, options: entry[2] }); } } return result; } - export function to(value: WorkspaceEditDto) { + export function to(value: IWorkspaceEditDto) { const result = new types.WorkspaceEdit(); for (const edit of value.edits) { - if (Array.isArray((edit).edits)) { + if (Array.isArray((edit).edits)) { result.set( - URI.revive((edit).resource), - (edit).edits.map(TextEdit.to) + URI.revive((edit).resource), + (edit).edits.map(TextEdit.to) ); } else { result.renameFile( - URI.revive((edit).oldUri!), - URI.revive((edit).newUri!), - (edit).options + URI.revive((edit).oldUri!), + URI.revive((edit).newUri!), + (edit).options ); } } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 464e19344a..70e9d5851e 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -514,7 +514,7 @@ export class TextEdit { protected _range: Range; protected _newText: string | null; - protected _newEol: EndOfLine; + protected _newEol?: EndOfLine; get range(): Range { return this._range; @@ -538,11 +538,11 @@ export class TextEdit { this._newText = value; } - get newEol(): EndOfLine { + get newEol(): EndOfLine | undefined { return this._newEol; } - set newEol(value: EndOfLine) { + set newEol(value: EndOfLine | undefined) { if (value && typeof value !== 'number') { throw illegalArgument('newEol'); } @@ -550,7 +550,7 @@ export class TextEdit { } constructor(range: Range, newText: string | null) { - this.range = range; + this._range = range; this._newText = newText; } @@ -798,7 +798,7 @@ export class Location { } uri: URI; - range: Range; + range!: Range; constructor(uri: URI, rangeOrPosition: Range | Position) { this.uri = uri; @@ -861,10 +861,10 @@ export class Diagnostic { range: Range; message: string; - source: string; - code: string | number; severity: DiagnosticSeverity; - relatedInformation: DiagnosticRelatedInformation[]; + source?: string; + code?: string | number; + relatedInformation?: DiagnosticRelatedInformation[]; tags?: DiagnosticTag[]; constructor(range: Range, message: string, severity: DiagnosticSeverity = DiagnosticSeverity.Error) { @@ -989,7 +989,7 @@ export class SymbolInformation { } name: string; - location: Location; + location!: Location; kind: SymbolKind; containerName: string | undefined; @@ -1075,6 +1075,8 @@ export class CodeAction { kind?: CodeActionKind; + isPreferred?: boolean; + constructor(title: string, kind?: CodeActionKind) { this.title = title; this.kind = kind; @@ -1253,8 +1255,8 @@ export class SignatureInformation { export class SignatureHelp { signatures: SignatureInformation[]; - activeSignature: number; - activeParameter: number; + activeSignature: number = 0; + activeParameter: number = 0; constructor() { this.signatures = []; @@ -1310,19 +1312,19 @@ export enum CompletionItemKind { export class CompletionItem implements vscode.CompletionItem { label: string; - kind: CompletionItemKind | undefined; + kind?: CompletionItemKind; detail?: string; documentation?: string | MarkdownString; sortText?: string; filterText?: string; preselect?: boolean; - insertText: string | SnippetString; + insertText?: string | SnippetString; keepWhitespace?: boolean; - range: Range; + range?: Range; commitCharacters?: string[]; - textEdit: TextEdit; - additionalTextEdits: TextEdit[]; - command: vscode.Command; + textEdit?: TextEdit; + additionalTextEdits?: TextEdit[]; + command?: vscode.Command; constructor(label: string, kind?: CompletionItemKind) { this.label = label; @@ -1753,26 +1755,6 @@ export enum TaskScope { Workspace = 2 } -export class CustomExecution implements vscode.CustomExecution { - private _callback: (args: vscode.TerminalRenderer, cancellationToken: vscode.CancellationToken) => Thenable; - - constructor(callback: (args: vscode.TerminalRenderer, cancellationToken: vscode.CancellationToken) => Thenable) { - this._callback = callback; - } - - public computeId(): string { - return 'customExecution' + generateUuid(); - } - - public set callback(value: (args: vscode.TerminalRenderer, cancellationToken: vscode.CancellationToken) => Thenable) { - this._callback = value; - } - - public get callback(): (args: vscode.TerminalRenderer, cancellationToken: vscode.CancellationToken) => Thenable { - return this._callback; - } -} - export class CustomExecution2 implements vscode.CustomExecution2 { private _callback: () => Thenable; constructor(callback: () => Thenable) { @@ -1804,7 +1786,7 @@ export class Task implements vscode.Task2 { private _definition: vscode.TaskDefinition; private _scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder | undefined; private _name: string; - private _execution: ProcessExecution | ShellExecution | CustomExecution | CustomExecution2 | undefined; + private _execution: ProcessExecution | ShellExecution | CustomExecution2 | undefined; private _problemMatchers: string[]; private _hasDefinedMatchers: boolean; private _isBackground: boolean; @@ -1813,8 +1795,8 @@ export class Task implements vscode.Task2 { private _presentationOptions: vscode.TaskPresentationOptions; private _runOptions: vscode.RunOptions; - constructor(definition: vscode.TaskDefinition, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution | CustomExecution2, problemMatchers?: string | string[]); - constructor(definition: vscode.TaskDefinition, scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution | CustomExecution2, problemMatchers?: string | string[]); + constructor(definition: vscode.TaskDefinition, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution2, problemMatchers?: string | string[]); + constructor(definition: vscode.TaskDefinition, scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution2, problemMatchers?: string | string[]); constructor(definition: vscode.TaskDefinition, arg2: string | (vscode.TaskScope.Global | vscode.TaskScope.Workspace) | vscode.WorkspaceFolder, arg3: any, arg4?: any, arg5?: any, arg6?: any) { this.definition = definition; let problemMatchers: string | string[]; @@ -1879,7 +1861,7 @@ export class Task implements vscode.Task2 { type: Task.ShellType, id: this._execution.computeId() }; - } else if (this._execution instanceof CustomExecution) { + } else if (this._execution instanceof CustomExecution2) { this._definition = { type: Task.ExtensionCallbackType, id: this._execution.computeId() @@ -1926,18 +1908,18 @@ export class Task implements vscode.Task2 { } get execution(): ProcessExecution | ShellExecution | undefined { - return ((this._execution instanceof CustomExecution) || (this._execution instanceof CustomExecution2)) ? undefined : this._execution; + return (this._execution instanceof CustomExecution2) ? undefined : this._execution; } set execution(value: ProcessExecution | ShellExecution | undefined) { this.execution2 = value; } - get execution2(): ProcessExecution | ShellExecution | CustomExecution | CustomExecution2 | undefined { + get execution2(): ProcessExecution | ShellExecution | CustomExecution2 | undefined { return this._execution; } - set execution2(value: ProcessExecution | ShellExecution | CustomExecution | CustomExecution2 | undefined) { + set execution2(value: ProcessExecution | ShellExecution | CustomExecution2 | undefined) { if (value === null) { value = undefined; } diff --git a/src/vs/workbench/api/common/extHostUriTransformerService.ts b/src/vs/workbench/api/common/extHostUriTransformerService.ts new file mode 100644 index 0000000000..1810ded74a --- /dev/null +++ b/src/vs/workbench/api/common/extHostUriTransformerService.ts @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IURITransformer } from 'vs/base/common/uriIpc'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { URI, UriComponents } from 'vs/base/common/uri'; + +export interface IURITransformerService extends IURITransformer { + _serviceBrand: any; +} + +export const IURITransformerService = createDecorator('IURITransformerService'); + +export class URITransformerService implements IURITransformerService { + _serviceBrand: any; + + transformIncoming: (uri: UriComponents) => UriComponents; + transformOutgoing: (uri: UriComponents) => UriComponents; + transformOutgoingURI: (uri: URI) => URI; + transformOutgoingScheme: (scheme: string) => string; + + constructor(delegate: IURITransformer | null) { + if (!delegate) { + this.transformIncoming = arg => arg; + this.transformOutgoing = arg => arg; + this.transformOutgoingURI = arg => arg; + this.transformOutgoingScheme = arg => arg; + } else { + this.transformIncoming = delegate.transformIncoming.bind(delegate); + this.transformOutgoing = delegate.transformOutgoing.bind(delegate); + this.transformOutgoingURI = delegate.transformOutgoingURI.bind(delegate); + this.transformOutgoingScheme = delegate.transformOutgoingScheme.bind(delegate); + } + } +} diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 717bfdd177..b928faaf29 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -20,11 +20,14 @@ import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspa import { Range, RelativePattern } from 'vs/workbench/api/common/extHostTypes'; import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder'; import * as vscode from 'vscode'; -import { ExtHostWorkspaceShape, IWorkspaceData, MainThreadMessageServiceShape, MainThreadWorkspaceShape, IMainContext, MainContext, IStaticWorkspaceData } from './extHost.protocol'; +import { ExtHostWorkspaceShape, IWorkspaceData, MainThreadMessageServiceShape, MainThreadWorkspaceShape, MainContext } from './extHost.protocol'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { Barrier } from 'vs/base/common/async'; import { Schemas } from 'vs/base/common/network'; import { withUndefinedAsNull } from 'vs/base/common/types'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; export interface IExtHostWorkspaceProvider { getWorkspaceFolder2(uri: vscode.Uri, resolveParent?: boolean): Promise; @@ -153,6 +156,8 @@ class ExtHostWorkspaceImpl extends Workspace { export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspaceProvider { + readonly _serviceBrand: any; + private readonly _onDidChangeWorkspace = new Emitter(); readonly onDidChangeWorkspace: Event = this._onDidChangeWorkspace.event; @@ -169,21 +174,21 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac private readonly _activeSearchCallbacks: ((match: IRawFileMatch2) => any)[] = []; constructor( - mainContext: IMainContext, - logService: ILogService, - requestIdProvider: Counter, - data?: IStaticWorkspaceData + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @IExtHostInitDataService initData: IExtHostInitDataService, + @ILogService logService: ILogService, ) { this._logService = logService; - this._requestIdProvider = requestIdProvider; + this._requestIdProvider = new Counter(); this._barrier = new Barrier(); - this._proxy = mainContext.getProxy(MainContext.MainThreadWorkspace); - this._messageService = mainContext.getProxy(MainContext.MainThreadMessageService); + this._proxy = extHostRpc.getProxy(MainContext.MainThreadWorkspace); + this._messageService = extHostRpc.getProxy(MainContext.MainThreadMessageService); + const data = initData.workspace; this._confirmedWorkspace = data ? new ExtHostWorkspaceImpl(data.id, data.name, [], data.configuration ? URI.revive(data.configuration) : null, !!data.isUntitled) : undefined; } - $initializeWorkspace(data: IWorkspaceData): void { + $initializeWorkspace(data: IWorkspaceData | null): void { this.$acceptWorkspaceData(data); this._barrier.open(); } @@ -289,7 +294,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac this._unconfirmedWorkspace = undefined; // show error to user - this._messageService.$showMessage(Severity.Error, localize('updateerror', "Extension '{0}' failed to update workspace folders: {1}", extName, error), { extension }, []); + this._messageService.$showMessage(Severity.Error, localize('updateerror', "Extension '{0}' failed to update workspace folders: {1}", extName, error.toString()), { extension }, []); }); } @@ -390,7 +395,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac } } - $acceptWorkspaceData(data: IWorkspaceData): void { + $acceptWorkspaceData(data: IWorkspaceData | null): void { const { workspace, added, removed } = ExtHostWorkspaceImpl.toExtHostWorkspace(data, this._confirmedWorkspace, this._unconfirmedWorkspace); @@ -546,3 +551,6 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac return this._proxy.$resolveProxy(url); } } + +export const IExtHostWorkspace = createDecorator('IExtHostWorkspace'); +export interface IExtHostWorkspace extends ExtHostWorkspace, ExtHostWorkspaceShape, IExtHostWorkspaceProvider { } diff --git a/src/vs/workbench/api/common/shared/tasks.ts b/src/vs/workbench/api/common/shared/tasks.ts index 557e9636e9..e4b52590e6 100644 --- a/src/vs/workbench/api/common/shared/tasks.ts +++ b/src/vs/workbench/api/common/shared/tasks.ts @@ -66,10 +66,6 @@ export interface ShellExecutionDTO { options?: ShellExecutionOptionsDTO; } -export interface CustomExecutionDTO { - customExecution: 'customExecution'; -} - export interface CustomExecution2DTO { customExecution: 'customExecution2'; } @@ -88,7 +84,7 @@ export interface TaskHandleDTO { export interface TaskDTO { _id: string; name?: string; - execution: ProcessExecutionDTO | ShellExecutionDTO | CustomExecutionDTO | CustomExecution2DTO | undefined; + execution: ProcessExecutionDTO | ShellExecutionDTO | CustomExecution2DTO | undefined; definition: TaskDefinitionDTO; isBackground?: boolean; source: TaskSourceDTO; @@ -129,4 +125,4 @@ export interface TaskSystemInfoDTO { scheme: string; authority: string; platform: string; -} \ No newline at end of file +} diff --git a/src/vs/workbench/api/node/extHost.services.ts b/src/vs/workbench/api/node/extHost.services.ts new file mode 100644 index 0000000000..9071ae1d91 --- /dev/null +++ b/src/vs/workbench/api/node/extHost.services.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IExtHostOutputService } from 'vs/workbench/api/common/extHostOutput'; +import { ExtHostOutputService2 } from 'vs/workbench/api/node/extHostOutputService'; +import { IExtHostWorkspace, ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; +import { IExtHostDecorations, ExtHostDecorations } from 'vs/workbench/api/common/extHostDecorations'; +import { IExtHostConfiguration, ExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; +import { IExtHostCommands, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { ExtHostTerminalService } from 'vs/workbench/api/node/extHostTerminalService'; +import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; +import { IExtHostTask } from 'vs/workbench/api/common/extHostTask'; +import { ExtHostTask } from 'vs/workbench/api/node/extHostTask'; +import { ExtHostDebugService } from 'vs/workbench/api/node/extHostDebugService'; +import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; +import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; +import { ExtHostSearch } from 'vs/workbench/api/node/extHostSearch'; +import { ExtensionStoragePaths } from 'vs/workbench/api/node/extHostStoragePaths'; +import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; +import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; +import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; +import { IExtHostStorage, ExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; + +// register singleton services +registerSingleton(IExtHostOutputService, ExtHostOutputService2); +registerSingleton(IExtHostWorkspace, ExtHostWorkspace); +registerSingleton(IExtHostDecorations, ExtHostDecorations); +registerSingleton(IExtHostConfiguration, ExtHostConfiguration); +registerSingleton(IExtHostCommands, ExtHostCommands); +registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors); +registerSingleton(IExtHostTerminalService, ExtHostTerminalService); +registerSingleton(IExtHostTask, ExtHostTask); +registerSingleton(IExtHostDebugService, ExtHostDebugService); +registerSingleton(IExtHostSearch, ExtHostSearch); +registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths); +registerSingleton(IExtHostExtensionService, ExtHostExtensionService); +registerSingleton(IExtHostStorage, ExtHostStorage); diff --git a/src/vs/workbench/api/node/extHostCLIServer.ts b/src/vs/workbench/api/node/extHostCLIServer.ts index 1f34aff476..e8ebbd307b 100644 --- a/src/vs/workbench/api/node/extHostCLIServer.ts +++ b/src/vs/workbench/api/node/extHostCLIServer.ts @@ -6,7 +6,7 @@ import { generateRandomPipeName } from 'vs/base/parts/ipc/node/ipc.net'; import * as http from 'http'; import * as fs from 'fs'; -import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { IURIToOpen, IOpenSettings } from 'vs/platform/windows/common/windows'; import { URI } from 'vs/base/common/uri'; import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; @@ -38,7 +38,7 @@ export class CLIServer { private _server: http.Server; private _ipcHandlePath: string | undefined; - constructor(private _commands: ExtHostCommands) { + constructor(@IExtHostCommands private _commands: IExtHostCommands) { this._server = http.createServer((req, res) => this.onRequest(req, res)); this.setup().catch(err => { console.error(err); @@ -179,4 +179,4 @@ export class CLIServer { fs.unlinkSync(this._ipcHandlePath); } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index ce0805dc58..114b9f3f90 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -11,33 +11,37 @@ import { asPromise } from 'vs/base/common/async'; import * as nls from 'vs/nls'; import { MainContext, MainThreadDebugServiceShape, ExtHostDebugServiceShape, DebugSessionUUID, - IMainContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto + IBreakpointsDeltaDto, ISourceMultiBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto } from 'vs/workbench/api/common/extHost.protocol'; import * as vscode from 'vscode'; import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable } from 'vs/workbench/api/common/extHostTypes'; import { ExecutableDebugAdapter, SocketDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter'; import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; -import { IExtHostWorkspaceProvider } from 'vs/workbench/api/common/extHostWorkspace'; -import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; -import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; +import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; +import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { IDebuggerContribution, IConfig, IDebugAdapter, IDebugAdapterServer, IDebugAdapterExecutable, IAdapterDescriptor } from 'vs/workbench/contrib/debug/common/debug'; import { hasChildProcesses, prepareCommand, runInExternalTerminal } from 'vs/workbench/contrib/debug/node/terminals'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver'; -import { ExtHostConfiguration, ExtHostConfigProvider } from '../common/extHostConfiguration'; +import { ExtHostConfigProvider, IExtHostConfiguration } from '../common/extHostConfiguration'; import { convertToVSCPaths, convertToDAPaths, isDebuggerMainContribution } from 'vs/workbench/contrib/debug/common/debugUtils'; -import { ExtHostTerminalService } from 'vs/workbench/api/node/extHostTerminalService'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { SignService } from 'vs/platform/sign/node/signService'; import { ISignService } from 'vs/platform/sign/common/sign'; +import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; -export class ExtHostDebugService implements ExtHostDebugServiceShape { +export class ExtHostDebugService implements IExtHostDebugService, ExtHostDebugServiceShape { + + readonly _serviceBrand: any; private _configProviderHandleCounter: number; private _configProviders: ConfigProviderTuple[]; @@ -86,13 +90,14 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { private _signService: ISignService; - constructor(mainContext: IMainContext, - private _workspaceService: IExtHostWorkspaceProvider, - private _extensionService: ExtHostExtensionService, - private _editorsService: ExtHostDocumentsAndEditors, - private _configurationService: ExtHostConfiguration, - private _terminalService: ExtHostTerminalService, - private _commandService: ExtHostCommands + constructor( + @IExtHostRpcService extHostRpcService: IExtHostRpcService, + @IExtHostWorkspace private _workspaceService: IExtHostWorkspace, + @IExtHostExtensionService private _extensionService: IExtHostExtensionService, + @IExtHostDocumentsAndEditors private _editorsService: IExtHostDocumentsAndEditors, + @IExtHostConfiguration private _configurationService: IExtHostConfiguration, + @IExtHostTerminalService private _terminalService: IExtHostTerminalService, + @IExtHostCommands private _commandService: IExtHostCommands ) { this._configProviderHandleCounter = 0; this._configProviders = []; @@ -112,7 +117,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { this._onDidChangeActiveDebugSession = new Emitter(); this._onDidReceiveDebugSessionCustomEvent = new Emitter(); - this._debugServiceProxy = mainContext.getProxy(MainContext.MainThreadDebugService); + this._debugServiceProxy = extHostRpcService.getProxy(MainContext.MainThreadDebugService); this._onDidChangeBreakpoints = new Emitter({ onFirstListenerAdd: () => { @@ -344,7 +349,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { }).then(async needNewTerminal => { const configProvider = await this._configurationService.getConfigProvider(); - const shell = this._terminalService.getDefaultShell(configProvider); + const shell = this._terminalService.getDefaultShell(true, configProvider); if (needNewTerminal || !this._integratedTerminalInstance) { const options: vscode.TerminalOptions = { @@ -599,7 +604,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { this.fireBreakpointChanges(a, r, c); } - public $provideDebugConfigurations(configProviderHandle: number, folderUri: UriComponents | undefined): Promise { + public $provideDebugConfigurations(configProviderHandle: number, folderUri: UriComponents | undefined, token: CancellationToken): Promise { return asPromise(async () => { const provider = this.getConfigProviderByHandle(configProviderHandle); if (!provider) { @@ -609,7 +614,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { throw new Error('DebugConfigurationProvider has no method provideDebugConfigurations'); } const folder = await this.getFolder(folderUri); - return provider.provideDebugConfigurations(folder, CancellationToken.None); + return provider.provideDebugConfigurations(folder, token); }).then(debugConfigurations => { if (!debugConfigurations) { throw new Error('nothing returned from DebugConfigurationProvider.provideDebugConfigurations'); @@ -618,7 +623,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { }); } - public $resolveDebugConfiguration(configProviderHandle: number, folderUri: UriComponents | undefined, debugConfiguration: vscode.DebugConfiguration): Promise { + public $resolveDebugConfiguration(configProviderHandle: number, folderUri: UriComponents | undefined, debugConfiguration: vscode.DebugConfiguration, token: CancellationToken): Promise { return asPromise(async () => { const provider = this.getConfigProviderByHandle(configProviderHandle); if (!provider) { @@ -628,7 +633,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { throw new Error('DebugConfigurationProvider has no method resolveDebugConfiguration'); } const folder = await this.getFolder(folderUri); - return provider.resolveDebugConfiguration(folder, debugConfiguration, CancellationToken.None); + return provider.resolveDebugConfiguration(folder, debugConfiguration, token); }); } diff --git a/src/vs/workbench/api/node/extHostDownloadService.ts b/src/vs/workbench/api/node/extHostDownloadService.ts index be81ee320f..f3408111ad 100644 --- a/src/vs/workbench/api/node/extHostDownloadService.ts +++ b/src/vs/workbench/api/node/extHostDownloadService.ts @@ -6,23 +6,26 @@ import { join } from 'vs/base/common/path'; import { tmpdir } from 'os'; import { generateUuid } from 'vs/base/common/uuid'; -import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { Disposable } from 'vs/base/common/lifecycle'; -import { MainThreadDownloadServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import { MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { URI } from 'vs/base/common/uri'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; export class ExtHostDownloadService extends Disposable { constructor( - proxy: MainThreadDownloadServiceShape, - commands: ExtHostCommands + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @IExtHostCommands commands: IExtHostCommands ) { super(); + + const proxy = extHostRpc.getProxy(MainContext.MainThreadDownloadService); + commands.registerCommand(false, '_workbench.downloadResource', async (resource: URI): Promise => { const location = URI.file(join(tmpdir(), generateUuid())); await proxy.$download(resource, location); return location; }); } - -} \ No newline at end of file +} diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index dd6790853e..651d958532 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -3,768 +3,63 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; -import * as path from 'vs/base/common/path'; -// {{SQL CARBON EDIT}} -import { createApiFactory, initializeExtensionApi, ISqlExtensionApiFactory } from 'sql/workbench/api/node/sqlExtHost.api.impl'; -import { originalFSPath } from 'vs/base/common/resources'; -import { Barrier } from 'vs/base/common/async'; -import { dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { TernarySearchTree } from 'vs/base/common/map'; -import { URI } from 'vs/base/common/uri'; -import { ILogService } from 'vs/platform/log/common/log'; -// {{SQL CARBON EDIT}} - Remove createApiFactory initializeExtensionApi, and IExtensionApiFactory imports -// import { createApiFactory, IExtensionApiFactory } from 'vs/workbench/api/node/extHost.api.impl'; +import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHost.api.impl'; import { NodeModuleRequireInterceptor, VSCodeNodeModuleFactory, KeytarNodeModuleFactory, OpenNodeModuleFactory } from 'vs/workbench/api/node/extHostRequireInterceptor'; -import { ExtHostExtensionServiceShape, IEnvironment, IInitData, IMainContext, MainContext, MainThreadExtensionServiceShape, MainThreadTelemetryShape, MainThreadWorkspaceShape, IResolveAuthorityResult } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; -import { ActivatedExtension, EmptyExtension, ExtensionActivatedByAPI, ExtensionActivatedByEvent, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionContext, IExtensionModule, HostExtension, ExtensionActivationTimesFragment } from 'vs/workbench/api/common/extHostExtensionActivator'; -import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService'; -import { ExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; -import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; -import { ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions'; -import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; +import { MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtensionActivationTimesBuilder } from 'vs/workbench/api/common/extHostExtensionActivator'; import { connectProxyResolver } from 'vs/workbench/services/extensions/node/proxyResolver'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import * as errors from 'vs/base/common/errors'; -import * as vscode from 'vscode'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { Schemas } from 'vs/base/common/network'; -import { withNullAsUndefined } from 'vs/base/common/types'; -import { VSBuffer } from 'vs/base/common/buffer'; -import { ExtensionMemento } from 'vs/workbench/api/common/extHostMemento'; -import { ExtensionStoragePaths } from 'vs/workbench/api/node/extHostStoragePaths'; -import { RemoteAuthorityResolverError, ExtensionExecutionContext } from 'vs/workbench/api/common/extHostTypes'; -import { IURITransformer } from 'vs/base/common/uriIpc'; -import { ResolvedAuthority, ResolvedOptions } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; +import { ExtHostDownloadService } from 'vs/workbench/api/node/extHostDownloadService'; +import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer'; +import { initializeExtensionApi, createApiFactory } from 'sql/workbench/api/common/sqlExtHost.api.impl'; // {{SQL CARBON EDIT}} use our extension initalizer -interface ITestRunner { - /** Old test runner API, as exported from `vscode/lib/testrunner` */ - run(testsRoot: string, clb: (error: Error, failures?: number) => void): void; -} +export class ExtHostExtensionService extends AbstractExtHostExtensionService { -interface INewTestRunner { - /** New test runner API, as explained in the extension test doc */ - run(): Promise; -} + protected async _beforeAlmostReadyToRunExtensions(): Promise { + // initialize API and register actors + const extensionApiFactory = this._instaService.invokeFunction(createApiFactory); -export interface IHostUtils { - exit(code?: number): void; - exists(path: string): Promise; - realpath(path: string): Promise; -} + // Register Download command + this._instaService.createInstance(ExtHostDownloadService); -type TelemetryActivationEventFragment = { - id: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; - name: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; - extensionVersion: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; - publisherDisplayName: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - activationEvents: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - isBuiltin: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - reason: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; -}; - -export class ExtHostExtensionService implements ExtHostExtensionServiceShape { - - private static readonly WORKSPACE_CONTAINS_TIMEOUT = 7000; - - private readonly _hostUtils: IHostUtils; - private readonly _initData: IInitData; - private readonly _extHostContext: IMainContext; - private readonly _extHostWorkspace: ExtHostWorkspace; - private readonly _extHostConfiguration: ExtHostConfiguration; - private readonly _environment: IEnvironment; - private readonly _extHostLogService: ExtHostLogService; - - private readonly _mainThreadWorkspaceProxy: MainThreadWorkspaceShape; - private readonly _mainThreadTelemetryProxy: MainThreadTelemetryShape; - private readonly _mainThreadExtensionsProxy: MainThreadExtensionServiceShape; - - private readonly _almostReadyToRunExtensions: Barrier; - private readonly _readyToStartExtensionHost: Barrier; - private readonly _readyToRunExtensions: Barrier; - private readonly _registry: ExtensionDescriptionRegistry; - private readonly _storage: ExtHostStorage; - private readonly _storagePath: ExtensionStoragePaths; - private readonly _activator: ExtensionsActivator; - private _extensionPathIndex: Promise> | null; - private readonly _extensionApiFactory: ISqlExtensionApiFactory; - - private readonly _resolvers: { [authorityPrefix: string]: vscode.RemoteAuthorityResolver; }; - - private _started: boolean; - - private readonly _disposables: DisposableStore; - - constructor( - hostUtils: IHostUtils, - initData: IInitData, - extHostContext: IMainContext, - extHostWorkspace: ExtHostWorkspace, - extHostConfiguration: ExtHostConfiguration, - environment: IEnvironment, - extHostLogService: ExtHostLogService, - uriTransformer: IURITransformer | null - ) { - this._hostUtils = hostUtils; - this._initData = initData; - this._extHostContext = extHostContext; - this._extHostWorkspace = extHostWorkspace; - this._extHostConfiguration = extHostConfiguration; - this._environment = environment; - this._extHostLogService = extHostLogService; - this._disposables = new DisposableStore(); - - this._mainThreadWorkspaceProxy = this._extHostContext.getProxy(MainContext.MainThreadWorkspace); - this._mainThreadTelemetryProxy = this._extHostContext.getProxy(MainContext.MainThreadTelemetry); - this._mainThreadExtensionsProxy = this._extHostContext.getProxy(MainContext.MainThreadExtensionService); - - this._almostReadyToRunExtensions = new Barrier(); - this._readyToStartExtensionHost = new Barrier(); - this._readyToRunExtensions = new Barrier(); - this._registry = new ExtensionDescriptionRegistry(initData.extensions); - this._storage = new ExtHostStorage(this._extHostContext); - this._storagePath = new ExtensionStoragePaths(withNullAsUndefined(initData.workspace), initData.environment); - - const hostExtensions = new Set(); - initData.hostExtensions.forEach((extensionId) => hostExtensions.add(ExtensionIdentifier.toKey(extensionId))); - - this._activator = new ExtensionsActivator(this._registry, initData.resolvedExtensions, initData.hostExtensions, { - onExtensionActivationError: (extensionId: ExtensionIdentifier, error: ExtensionActivationError): void => { - this._mainThreadExtensionsProxy.$onExtensionActivationError(extensionId, error); - }, - - actualActivateExtension: async (extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise => { - if (hostExtensions.has(ExtensionIdentifier.toKey(extensionId))) { - const activationEvent = (reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : null); - await this._mainThreadExtensionsProxy.$activateExtension(extensionId, activationEvent); - return new HostExtension(); - } - const extensionDescription = this._registry.getExtensionDescription(extensionId)!; - return this._activateExtension(extensionDescription, reason); - } - }); - this._extensionPathIndex = null; - - // initialize API first (i.e. do not release barrier until the API is initialized) - this._extensionApiFactory = createApiFactory( - this._initData, - this._extHostContext, - this._extHostWorkspace, - this._extHostConfiguration, - this, - this._extHostLogService, - this._storage, - uriTransformer - ); - - this._resolvers = Object.create(null); - - this._started = false; - - this._initialize(); - - if (this._initData.autoStart) { - this._startExtensionHost(); + // Register CLI Server for ipc + if (this._initData.remote.isRemote && this._initData.remote.authority) { + const cliServer = this._instaService.createInstance(CLIServer); + process.env['VSCODE_IPC_HOOK_CLI'] = cliServer.ipcHandlePath; } + + // Module loading tricks + const configProvider = await this._extHostConfiguration.getConfigProvider(); + const extensionPaths = await this.getExtensionPathIndex(); + // {{SQL CARBON EDIT}} - disable VSCodeNodeModuleFactory and use older initializeExtensionApi + // NodeModuleRequireInterceptor.INSTANCE.register(new VSCodeNodeModuleFactory(this._extensionApiFactory, extensionPaths, this._registry, configProvider)); + await initializeExtensionApi(this, extensionApiFactory, this._registry, configProvider); + NodeModuleRequireInterceptor.INSTANCE.register(new KeytarNodeModuleFactory(this._extHostContext.getProxy(MainContext.MainThreadKeytar), this._initData.environment)); + if (this._initData.remote.isRemote) { + NodeModuleRequireInterceptor.INSTANCE.register(new OpenNodeModuleFactory( + this._extHostContext.getProxy(MainContext.MainThreadWindow), + this._extHostContext.getProxy(MainContext.MainThreadTelemetry), + extensionPaths + )); + } + + // Do this when extension service exists, but extensions are not being activated yet. + await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._extHostLogService, this._mainThreadTelemetryProxy); + } - private async _initialize(): Promise { + protected _loadCommonJSModule(modulePath: string, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { + let r: T | null = null; + activationTimesBuilder.codeLoadingStart(); + this._extHostLogService.info(`ExtensionService#loadCommonJSModule ${modulePath}`); try { - const configProvider = await this._extHostConfiguration.getConfigProvider(); - const extensionPaths = await this.getExtensionPathIndex(); - // {{SQL CARBON EDIT}} - disable VSCodeNodeModuleFactory and use older initializeExtensionApi - // NodeModuleRequireInterceptor.INSTANCE.register(new VSCodeNodeModuleFactory(this._extensionApiFactory, extensionPaths, this._registry, configProvider)); - await initializeExtensionApi(this, this._extensionApiFactory, this._registry, configProvider); - NodeModuleRequireInterceptor.INSTANCE.register(new KeytarNodeModuleFactory(this._extHostContext.getProxy(MainContext.MainThreadKeytar), this._environment)); - if (this._initData.remote.isRemote) { - NodeModuleRequireInterceptor.INSTANCE.register(new OpenNodeModuleFactory( - this._extHostContext.getProxy(MainContext.MainThreadWindow), - this._extHostContext.getProxy(MainContext.MainThreadTelemetry), - extensionPaths - )); - } - - // Do this when extension service exists, but extensions are not being activated yet. - await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._extHostLogService, this._mainThreadTelemetryProxy); - this._almostReadyToRunExtensions.open(); - - await this._extHostWorkspace.waitForInitializeCall(); - this._readyToStartExtensionHost.open(); - } catch (err) { - errors.onUnexpectedError(err); + r = require.__$__nodeRequire(modulePath); + } catch (e) { + return Promise.reject(e); + } finally { + activationTimesBuilder.codeLoadingStop(); } - } - - public async deactivateAll(): Promise { - let allPromises: Promise[] = []; - try { - const allExtensions = this._registry.getAllExtensionDescriptions(); - const allExtensionsIds = allExtensions.map(ext => ext.identifier); - const activatedExtensions = allExtensionsIds.filter(id => this.isActivated(id)); - - allPromises = activatedExtensions.map((extensionId) => { - return this._deactivate(extensionId); - }); - } catch (err) { - // TODO: write to log once we have one - } - await allPromises; - } - - public isActivated(extensionId: ExtensionIdentifier): boolean { - if (this._readyToRunExtensions.isOpen()) { - return this._activator.isActivated(extensionId); - } - return false; - } - - private _activateByEvent(activationEvent: string, startup: boolean): Promise { - const reason = new ExtensionActivatedByEvent(startup, activationEvent); - return this._activator.activateByEvent(activationEvent, reason); - } - - private _activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { - return this._activator.activateById(extensionId, reason); - } - - public activateByIdWithErrors(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { - return this._activateById(extensionId, reason).then(() => { - const extension = this._activator.getActivatedExtension(extensionId); - if (extension.activationFailed) { - // activation failed => bubble up the error as the promise result - return Promise.reject(extension.activationFailedError); - } - return undefined; - }); - } - - public getExtensionRegistry(): Promise { - return this._readyToRunExtensions.wait().then(_ => this._registry); - } - - public getExtensionExports(extensionId: ExtensionIdentifier): IExtensionAPI | null | undefined { - if (this._readyToRunExtensions.isOpen()) { - return this._activator.getActivatedExtension(extensionId).exports; - } else { - return null; - } - } - - // create trie to enable fast 'filename -> extension id' look up - public getExtensionPathIndex(): Promise> { - if (!this._extensionPathIndex) { - const tree = TernarySearchTree.forPaths(); - const extensions = this._registry.getAllExtensionDescriptions().map(ext => { - if (!ext.main) { - return undefined; - } - return this._hostUtils.realpath(ext.extensionLocation.fsPath).then(value => tree.set(URI.file(value).fsPath, ext)); - }); - this._extensionPathIndex = Promise.all(extensions).then(() => tree); - } - return this._extensionPathIndex; - } - - private _deactivate(extensionId: ExtensionIdentifier): Promise { - let result = Promise.resolve(undefined); - - if (!this._readyToRunExtensions.isOpen()) { - return result; - } - - if (!this._activator.isActivated(extensionId)) { - return result; - } - - const extension = this._activator.getActivatedExtension(extensionId); - if (!extension) { - return result; - } - - // call deactivate if available - try { - if (typeof extension.module.deactivate === 'function') { - result = Promise.resolve(extension.module.deactivate()).then(undefined, (err) => { - // TODO: Do something with err if this is not the shutdown case - return Promise.resolve(undefined); - }); - } - } catch (err) { - // TODO: Do something with err if this is not the shutdown case - } - - // clean up subscriptions - try { - dispose(extension.subscriptions); - } catch (err) { - // TODO: Do something with err if this is not the shutdown case - } - - return result; - } - - // --- impl - - private _activateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise { - this._mainThreadExtensionsProxy.$onWillActivateExtension(extensionDescription.identifier); - return this._doActivateExtension(extensionDescription, reason).then((activatedExtension) => { - const activationTimes = activatedExtension.activationTimes; - const activationEvent = (reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : null); - this._mainThreadExtensionsProxy.$onDidActivateExtension(extensionDescription.identifier, activationTimes.startup, activationTimes.codeLoadingTime, activationTimes.activateCallTime, activationTimes.activateResolvedTime, activationEvent); - this._logExtensionActivationTimes(extensionDescription, reason, 'success', activationTimes); - return activatedExtension; - }, (err) => { - this._logExtensionActivationTimes(extensionDescription, reason, 'failure'); - throw err; - }); - } - - private _logExtensionActivationTimes(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason, outcome: string, activationTimes?: ExtensionActivationTimes) { - const event = getTelemetryActivationEvent(extensionDescription, reason); - type ExtensionActivationTimesClassification = { - outcome: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - } & TelemetryActivationEventFragment & ExtensionActivationTimesFragment; - - type ExtensionActivationTimesEvent = { - outcome: string - } & ActivationTimesEvent & TelemetryActivationEvent; - - type ActivationTimesEvent = { - startup?: boolean; - codeLoadingTime?: number; - activateCallTime?: number; - activateResolvedTime?: number; - }; - - this._mainThreadTelemetryProxy.$publicLog2('extensionActivationTimes', { - ...event, - ...(activationTimes || {}), - outcome - }); - } - - private _doActivateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise { - const event = getTelemetryActivationEvent(extensionDescription, reason); - type ActivatePluginClassification = {} & TelemetryActivationEventFragment; - this._mainThreadTelemetryProxy.$publicLog2('activatePlugin', event); - if (!extensionDescription.main) { - // Treat the extension as being empty => NOT AN ERROR CASE - return Promise.resolve(new EmptyExtension(ExtensionActivationTimes.NONE)); - } - - this._extHostLogService.info(`ExtensionService#_doActivateExtension ${extensionDescription.identifier.value} ${JSON.stringify(reason)}`); - - const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup); - return Promise.all([ - loadCommonJSModule(this._extHostLogService, extensionDescription.main, activationTimesBuilder), - this._loadExtensionContext(extensionDescription) - ]).then(values => { - return ExtHostExtensionService._callActivate(this._extHostLogService, extensionDescription.identifier, values[0], values[1], activationTimesBuilder); - }); - } - - private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise { - - const globalState = new ExtensionMemento(extensionDescription.identifier.value, true, this._storage); - const workspaceState = new ExtensionMemento(extensionDescription.identifier.value, false, this._storage); - - this._extHostLogService.trace(`ExtensionService#loadExtensionContext ${extensionDescription.identifier.value}`); - return Promise.all([ - globalState.whenReady, - workspaceState.whenReady, - this._storagePath.whenReady - ]).then(() => { - const that = this; - return Object.freeze({ - globalState, - workspaceState, - subscriptions: [], - get extensionPath() { return extensionDescription.extensionLocation.fsPath; }, - storagePath: this._storagePath.workspaceValue(extensionDescription), - globalStoragePath: this._storagePath.globalValue(extensionDescription), - asAbsolutePath: (relativePath: string) => { return path.join(extensionDescription.extensionLocation.fsPath, relativePath); }, - logPath: that._extHostLogService.getLogDirectory(extensionDescription.identifier), - executionContext: this._initData.remote.isRemote ? ExtensionExecutionContext.Remote : ExtensionExecutionContext.Local, - }); - }); - } - - private static _callActivate(logService: ILogService, extensionId: ExtensionIdentifier, extensionModule: IExtensionModule, context: IExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { - // Make sure the extension's surface is not undefined - extensionModule = extensionModule || { - activate: undefined, - deactivate: undefined - }; - - return this._callActivateOptional(logService, extensionId, extensionModule, context, activationTimesBuilder).then((extensionExports) => { - return new ActivatedExtension(false, null, activationTimesBuilder.build(), extensionModule, extensionExports, context.subscriptions); - }); - } - - private static _callActivateOptional(logService: ILogService, extensionId: ExtensionIdentifier, extensionModule: IExtensionModule, context: IExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { - if (typeof extensionModule.activate === 'function') { - try { - activationTimesBuilder.activateCallStart(); - logService.trace(`ExtensionService#_callActivateOptional ${extensionId.value}`); - const activateResult: Promise = extensionModule.activate.apply(global, [context]); - activationTimesBuilder.activateCallStop(); - - activationTimesBuilder.activateResolveStart(); - return Promise.resolve(activateResult).then((value) => { - activationTimesBuilder.activateResolveStop(); - return value; - }); - } catch (err) { - return Promise.reject(err); - } - } else { - // No activate found => the module is the extension's exports - return Promise.resolve(extensionModule); - } - } - - // -- eager activation - - // Handle "eager" activation extensions - private async _handleEagerExtensions(): Promise { - // {{SQL CARBON EDIT}} - load MSSQL extension first so it doesn't get delayed by other extensions - try { - await this._activateById(new ExtensionIdentifier('microsoft.mssql'), new ExtensionActivatedByEvent(true, `Load MSSQL extension first on startup`)); - } catch (err) { - console.error(err); - } - - return this._defaultHandleEagerExtensions(); - } - - - // Handle "eager" activation extensions - private _defaultHandleEagerExtensions(): Promise { - this._activateByEvent('*', true).then(undefined, (err) => { - console.error(err); - }); - - this._disposables.add(this._extHostWorkspace.onDidChangeWorkspace((e) => this._handleWorkspaceContainsEagerExtensions(e.added))); - const folders = this._extHostWorkspace.workspace ? this._extHostWorkspace.workspace.folders : []; - return this._handleWorkspaceContainsEagerExtensions(folders); - } - - private _handleWorkspaceContainsEagerExtensions(folders: ReadonlyArray): Promise { - if (folders.length === 0) { - return Promise.resolve(undefined); - } - - return Promise.all( - this._registry.getAllExtensionDescriptions().map((desc) => { - return this._handleWorkspaceContainsEagerExtension(folders, desc); - }) - ).then(() => { }); - } - - private _handleWorkspaceContainsEagerExtension(folders: ReadonlyArray, desc: IExtensionDescription): Promise { - const activationEvents = desc.activationEvents; - if (!activationEvents) { - return Promise.resolve(undefined); - } - - if (this.isActivated(desc.identifier)) { - return Promise.resolve(undefined); - } - - const fileNames: string[] = []; - const globPatterns: string[] = []; - - for (const activationEvent of activationEvents) { - if (/^workspaceContains:/.test(activationEvent)) { - const fileNameOrGlob = activationEvent.substr('workspaceContains:'.length); - if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0) { - globPatterns.push(fileNameOrGlob); - } else { - fileNames.push(fileNameOrGlob); - } - } - } - - if (fileNames.length === 0 && globPatterns.length === 0) { - return Promise.resolve(undefined); - } - - const fileNamePromise = Promise.all(fileNames.map((fileName) => this._activateIfFileName(folders, desc.identifier, fileName))).then(() => { }); - const globPatternPromise = this._activateIfGlobPatterns(folders, desc.identifier, globPatterns); - - return Promise.all([fileNamePromise, globPatternPromise]).then(() => { }); - } - - private async _activateIfFileName(folders: ReadonlyArray, extensionId: ExtensionIdentifier, fileName: string): Promise { - - // find exact path - for (const { uri } of folders) { - if (await this._hostUtils.exists(path.join(URI.revive(uri).fsPath, fileName))) { - // the file was found - return ( - this._activateById(extensionId, new ExtensionActivatedByEvent(true, `workspaceContains:${fileName}`)) - .then(undefined, err => console.error(err)) - ); - } - } - - return undefined; - } - - private async _activateIfGlobPatterns(folders: ReadonlyArray, extensionId: ExtensionIdentifier, globPatterns: string[]): Promise { - this._extHostLogService.trace(`extensionHostMain#activateIfGlobPatterns: fileSearch, extension: ${extensionId.value}, entryPoint: workspaceContains`); - - if (globPatterns.length === 0) { - return Promise.resolve(undefined); - } - - const tokenSource = new CancellationTokenSource(); - const searchP = this._mainThreadWorkspaceProxy.$checkExists(folders.map(folder => folder.uri), globPatterns, tokenSource.token); - - const timer = setTimeout(async () => { - tokenSource.cancel(); - this._activateById(extensionId, new ExtensionActivatedByEvent(true, `workspaceContainsTimeout:${globPatterns.join(',')}`)) - .then(undefined, err => console.error(err)); - }, ExtHostExtensionService.WORKSPACE_CONTAINS_TIMEOUT); - - let exists: boolean = false; - try { - exists = await searchP; - } catch (err) { - if (!errors.isPromiseCanceledError(err)) { - console.error(err); - } - } - - tokenSource.dispose(); - clearTimeout(timer); - - if (exists) { - // a file was found matching one of the glob patterns - return ( - this._activateById(extensionId, new ExtensionActivatedByEvent(true, `workspaceContains:${globPatterns.join(',')}`)) - .then(undefined, err => console.error(err)) - ); - } - - return Promise.resolve(undefined); - } - - private _handleExtensionTests(): Promise { - return this._doHandleExtensionTests().then(undefined, error => { - console.error(error); // ensure any error message makes it onto the console - - return Promise.reject(error); - }); - } - - private _doHandleExtensionTests(): Promise { - const { extensionDevelopmentLocationURI: extensionDevelopmentLocationURI, extensionTestsLocationURI } = this._initData.environment; - if (!(extensionDevelopmentLocationURI && extensionTestsLocationURI && extensionTestsLocationURI.scheme === Schemas.file)) { - return Promise.resolve(undefined); - } - - const extensionTestsPath = originalFSPath(extensionTestsLocationURI); - - // Require the test runner via node require from the provided path - let testRunner: ITestRunner | INewTestRunner | undefined; - let requireError: Error | undefined; - try { - testRunner = require.__$__nodeRequire(extensionTestsPath); - } catch (error) { - requireError = error; - } - - // Execute the runner if it follows the old `run` spec - if (testRunner && typeof testRunner.run === 'function') { - return new Promise((c, e) => { - const oldTestRunnerCallback = (error: Error, failures: number | undefined) => { - if (error) { - e(error.toString()); - } else { - c(undefined); - } - - // after tests have run, we shutdown the host - this._gracefulExit(error || (typeof failures === 'number' && failures > 0) ? 1 /* ERROR */ : 0 /* OK */); - }; - - const runResult = testRunner!.run(extensionTestsPath, oldTestRunnerCallback); - - // Using the new API `run(): Promise` - if (runResult && runResult.then) { - runResult - .then(() => { - c(); - this._gracefulExit(0); - }) - .catch((err: Error) => { - e(err.toString()); - this._gracefulExit(1); - }); - } - }); - } - - // Otherwise make sure to shutdown anyway even in case of an error - else { - this._gracefulExit(1 /* ERROR */); - } - - return Promise.reject(new Error(requireError ? requireError.toString() : nls.localize('extensionTestError', "Path {0} does not point to a valid extension test runner.", extensionTestsPath))); - } - - private _gracefulExit(code: number): void { - // to give the PH process a chance to flush any outstanding console - // messages to the main process, we delay the exit() by some time - setTimeout(() => { - // If extension tests are running, give the exit code to the renderer - if (this._initData.remote.isRemote && !!this._initData.environment.extensionTestsLocationURI) { - this._mainThreadExtensionsProxy.$onExtensionHostExit(code); - return; - } - - this._hostUtils.exit(code); - }, 500); - } - - private _startExtensionHost(): Promise { - if (this._started) { - throw new Error(`Extension host is already started!`); - } - this._started = true; - - return this._readyToStartExtensionHost.wait() - .then(() => this._readyToRunExtensions.open()) - .then(() => this._handleEagerExtensions()) - .then(() => this._handleExtensionTests()) - .then(() => { - this._extHostLogService.info(`eager extensions activated`); - }); - } - - // -- called by extensions - - public registerRemoteAuthorityResolver(authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver): vscode.Disposable { - this._resolvers[authorityPrefix] = resolver; - return toDisposable(() => { - delete this._resolvers[authorityPrefix]; - }); - } - - // -- called by main thread - - public async $resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise { - const authorityPlusIndex = remoteAuthority.indexOf('+'); - if (authorityPlusIndex === -1) { - throw new Error(`Not an authority that can be resolved!`); - } - const authorityPrefix = remoteAuthority.substr(0, authorityPlusIndex); - - await this._almostReadyToRunExtensions.wait(); - await this._activateByEvent(`onResolveRemoteAuthority:${authorityPrefix}`, false); - - const resolver = this._resolvers[authorityPrefix]; - if (!resolver) { - throw new Error(`No remote extension installed to resolve ${authorityPrefix}.`); - } - - try { - const result = await resolver.resolve(remoteAuthority, { resolveAttempt }); - - // Split merged API result into separate authority/options - const authority: ResolvedAuthority = { - authority: remoteAuthority, - host: result.host, - port: result.port - }; - const options: ResolvedOptions = { - extensionHostEnv: result.extensionHostEnv - }; - - return { - type: 'ok', - value: { - authority, - options - } - }; - } catch (err) { - if (err instanceof RemoteAuthorityResolverError) { - return { - type: 'error', - error: { - code: err._code, - message: err._message, - detail: err._detail - } - }; - } - throw err; - } - } - - public $startExtensionHost(enabledExtensionIds: ExtensionIdentifier[]): Promise { - this._registry.keepOnly(enabledExtensionIds); - return this._startExtensionHost(); - } - - public $activateByEvent(activationEvent: string): Promise { - return ( - this._readyToRunExtensions.wait() - .then(_ => this._activateByEvent(activationEvent, false)) - ); - } - - public async $activate(extensionId: ExtensionIdentifier, activationEvent: string): Promise { - await this._readyToRunExtensions.wait(); - if (!this._registry.getExtensionDescription(extensionId)) { - // unknown extension => ignore - return false; - } - await this._activateById(extensionId, new ExtensionActivatedByEvent(false, activationEvent)); - return true; - } - - public async $deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise { - toAdd.forEach((extension) => (extension).extensionLocation = URI.revive(extension.extensionLocation)); - - const trie = await this.getExtensionPathIndex(); - - await Promise.all(toRemove.map(async (extensionId) => { - const extensionDescription = this._registry.getExtensionDescription(extensionId); - if (!extensionDescription) { - return; - } - const realpathValue = await this._hostUtils.realpath(extensionDescription.extensionLocation.fsPath); - trie.delete(URI.file(realpathValue).fsPath); - })); - - await Promise.all(toAdd.map(async (extensionDescription) => { - const realpathValue = await this._hostUtils.realpath(extensionDescription.extensionLocation.fsPath); - trie.set(URI.file(realpathValue).fsPath, extensionDescription); - })); - - this._registry.deltaExtensions(toAdd, toRemove); - return Promise.resolve(undefined); - } - - public async $test_latency(n: number): Promise { - return n; - } - - public async $test_up(b: VSBuffer): Promise { - return b.byteLength; - } - - public async $test_down(size: number): Promise { - let buff = VSBuffer.alloc(size); - let value = Math.random() % 256; - for (let i = 0; i < size; i++) { - buff.writeUInt8(value, i); - } - return buff; + return Promise.resolve(r); } public async $setRemoteEnvironment(env: { [key: string]: string | null }): Promise { @@ -782,44 +77,3 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { } } } - -function loadCommonJSModule(logService: ILogService, modulePath: string, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { - let r: T | null = null; - activationTimesBuilder.codeLoadingStart(); - logService.info(`ExtensionService#loadCommonJSModule ${modulePath}`); - try { - r = require.__$__nodeRequire(modulePath); - } catch (e) { - return Promise.reject(e); - } finally { - activationTimesBuilder.codeLoadingStop(); - } - return Promise.resolve(r); -} - -type TelemetryActivationEvent = { - id: string; - name: string; - extensionVersion: string; - publisherDisplayName: string; - activationEvents: string | null; - isBuiltin: boolean; - reason: string; -}; - -function getTelemetryActivationEvent(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): TelemetryActivationEvent { - const reasonStr = reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : - reason instanceof ExtensionActivatedByAPI ? 'api' : - ''; - const event = { - id: extensionDescription.identifier.value, - name: extensionDescription.name, - extensionVersion: extensionDescription.version, - publisherDisplayName: extensionDescription.publisher, - activationEvents: extensionDescription.activationEvents ? extensionDescription.activationEvents.join(',') : null, - isBuiltin: extensionDescription.isBuiltin, - reason: reasonStr - }; - - return event; -} diff --git a/src/vs/workbench/api/node/extHostOutputService.ts b/src/vs/workbench/api/node/extHostOutputService.ts index 0114cf10d9..6650d84a66 100644 --- a/src/vs/workbench/api/node/extHostOutputService.ts +++ b/src/vs/workbench/api/node/extHostOutputService.ts @@ -10,7 +10,9 @@ import { join } from 'vs/base/common/path'; import { OutputAppender } from 'vs/workbench/services/output/node/outputAppender'; import { toLocalISOString } from 'vs/base/common/date'; import { dirExists, mkdirp } from 'vs/base/node/pfs'; -import { AbstractExtHostOutputChannel, IOutputChannelFactory, ExtHostPushOutputChannel } from 'vs/workbench/api/common/extHostOutput'; +import { AbstractExtHostOutputChannel, ExtHostPushOutputChannel, ExtHostOutputService, LazyOutputChannel } from 'vs/workbench/api/common/extHostOutput'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; export class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChannel { @@ -43,23 +45,39 @@ export class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChann } } -export const LogOutputChannelFactory = new class implements IOutputChannelFactory { +export class ExtHostOutputService2 extends ExtHostOutputService { - _namePool = 1; + private _logsLocation: URI; + private _namePool: number = 1; - async createOutputChannel(name: string, logsLocation: URI, proxy: MainThreadOutputServiceShape): Promise { + constructor( + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @IExtHostInitDataService initData: IExtHostInitDataService, + ) { + super(extHostRpc); + this._logsLocation = initData.logsLocation; + } + + createOutputChannel(name: string): vscode.OutputChannel { + name = name.trim(); + if (!name) { + throw new Error('illegal argument `name`. must not be falsy'); + } + return new LazyOutputChannel(name, this._doCreateOutChannel(name)); + } + + private async _doCreateOutChannel(name: string): Promise { try { - const outputDirPath = join(logsLocation.fsPath, `output_logging_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); - const outputDir = await dirExists(outputDirPath).then(exists => exists ? exists : mkdirp(outputDirPath).then(() => true)).then(() => outputDirPath); + const outputDirPath = join(this._logsLocation.fsPath, `output_logging_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); + const outputDir = await dirExists(outputDirPath).then(exists => exists || mkdirp(outputDirPath).then(() => true)).then(() => outputDirPath); const fileName = `${this._namePool++}-${name.replace(/[\\/:\*\?"<>\|]/g, '')}`; const file = URI.file(join(outputDir, `${fileName}.log`)); const appender = new OutputAppender(fileName, file.fsPath); - return new ExtHostOutputChannelBackedByFile(name, appender, proxy); + return new ExtHostOutputChannelBackedByFile(name, appender, this._proxy); } catch (error) { // Do not crash if logger cannot be created console.log(error); - return new ExtHostPushOutputChannel(name, proxy); + return new ExtHostPushOutputChannel(name, this._proxy); } } -}; - +} diff --git a/src/vs/workbench/api/node/extHostRequireInterceptor.ts b/src/vs/workbench/api/node/extHostRequireInterceptor.ts index fc414e778c..c737cde115 100644 --- a/src/vs/workbench/api/node/extHostRequireInterceptor.ts +++ b/src/vs/workbench/api/node/extHostRequireInterceptor.ts @@ -12,7 +12,7 @@ import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/c import * as vscode from 'vscode'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { endsWith } from 'vs/base/common/strings'; -import { IExtensionApiFactory } from 'vs/workbench/api/node/extHost.api.impl'; +import { IExtensionApiFactory } from 'vs/workbench/api/common/extHost.api.impl'; interface LoadFunction { @@ -75,7 +75,7 @@ export class VSCodeNodeModuleFactory implements INodeModuleFactory { public readonly nodeModuleName = 'vscode'; private readonly _extApiImpl = new Map(); - private _defaultApiImpl: typeof vscode; + private _defaultApiImpl?: typeof vscode; constructor( private readonly _apiFactory: IExtensionApiFactory, @@ -191,7 +191,7 @@ export class OpenNodeModuleFactory implements INodeModuleFactory { public readonly nodeModuleName: string[] = ['open', 'opn']; private _extensionId: string | undefined; - private _original: IOriginalOpen; + private _original?: IOriginalOpen; private _impl: IOpenModule; constructor(mainThreadWindow: MainThreadWindowShape, private _mainThreadTelemerty: MainThreadTelemetryShape, private readonly _extensionPaths: TernarySearchTree) { @@ -224,7 +224,7 @@ export class OpenNodeModuleFactory implements INodeModuleFactory { private callOriginal(target: string, options: OpenOptions | undefined): Thenable { this.sendNoForwardTelemetry(); - return this._original(target, options); + return this._original!(target, options); } private sendShimmingTelemetry(): void { diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index 238a2c71f3..d3baf488aa 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -15,8 +15,10 @@ import { RipgrepSearchProvider } from 'vs/workbench/services/search/node/ripgrep import { OutputChannel } from 'vs/workbench/services/search/node/ripgrepSearchUtils'; import { TextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; import * as vscode from 'vscode'; -import { ExtHostSearchShape, IMainContext, MainContext, MainThreadSearchShape } from '../common/extHost.protocol'; -import { IURITransformer } from 'vs/base/common/uriIpc'; +import { ExtHostSearchShape, MainContext, MainThreadSearchShape } from '../common/extHost.protocol'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; export class ExtHostSearch implements ExtHostSearchShape { @@ -32,16 +34,30 @@ export class ExtHostSearch implements ExtHostSearchShape { private _fileSearchManager: FileSearchManager; - constructor(mainContext: IMainContext, private _uriTransformer: IURITransformer | null, private _logService: ILogService, private _pfs = pfs) { - this._proxy = mainContext.getProxy(MainContext.MainThreadSearch); + protected _pfs: typeof pfs = pfs; // allow extending for tests + + constructor( + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @IExtHostInitDataService initData: IExtHostInitDataService, + @IURITransformerService private _uriTransformer: IURITransformerService, + @ILogService private _logService: ILogService, + ) { + this._proxy = extHostRpc.getProxy(MainContext.MainThreadSearch); this._fileSearchManager = new FileSearchManager(); + + if (initData.remote.isRemote && initData.remote.authority) { + this._registerEHSearchProviders(); + } + } + + private _registerEHSearchProviders(): void { + const outputChannel = new OutputChannel(this._logService); + this.registerTextSearchProvider('file', new RipgrepSearchProvider(outputChannel)); + this.registerInternalFileSearchProvider('file', new SearchService()); } private _transformScheme(scheme: string): string { - if (this._uriTransformer) { - return this._uriTransformer.transformOutgoingScheme(scheme); - } - return scheme; + return this._uriTransformer.transformOutgoingScheme(scheme); } registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProvider): IDisposable { @@ -148,12 +164,6 @@ export class ExtHostSearch implements ExtHostSearchShape { } } -export function registerEHSearchProviders(extHostSearch: ExtHostSearch, logService: ILogService): void { - const outputChannel = new OutputChannel(logService); - extHostSearch.registerTextSearchProvider('file', new RipgrepSearchProvider(outputChannel)); - extHostSearch.registerInternalFileSearchProvider('file', new SearchService()); -} - function reviveQuery(rawQuery: U): U extends IRawTextQuery ? ITextQuery : IFileQuery { return { ...rawQuery, // TODO diff --git a/src/vs/workbench/api/node/extHostStoragePaths.ts b/src/vs/workbench/api/node/extHostStoragePaths.ts index 8877554eb8..19cf948cc3 100644 --- a/src/vs/workbench/api/node/extHostStoragePaths.ts +++ b/src/vs/workbench/api/node/extHostStoragePaths.ts @@ -8,23 +8,24 @@ import { URI } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; import { IEnvironment, IStaticWorkspaceData } from 'vs/workbench/api/common/extHost.protocol'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { withNullAsUndefined } from 'vs/base/common/types'; -export class ExtensionStoragePaths { +export class ExtensionStoragePaths implements IExtensionStoragePaths { + + readonly _serviceBrand: undefined; private readonly _workspace?: IStaticWorkspaceData; private readonly _environment: IEnvironment; - private readonly _ready: Promise; + readonly whenReady: Promise; private _value?: string; - constructor(workspace: IStaticWorkspaceData | undefined, environment: IEnvironment) { - this._workspace = workspace; - this._environment = environment; - this._ready = this._getOrCreateWorkspaceStoragePath().then(value => this._value = value); - } - - get whenReady(): Promise { - return this._ready; + constructor(@IExtHostInitDataService initData: IExtHostInitDataService) { + this._workspace = withNullAsUndefined(initData.workspace); + this._environment = initData.environment; + this.whenReady = this._getOrCreateWorkspaceStoragePath().then(value => this._value = value); } workspaceValue(extension: IExtensionDescription): string | undefined { diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index 360f441581..a47208a3da 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -12,28 +12,28 @@ import { Event, Emitter } from 'vs/base/common/event'; import { win32 } from 'vs/base/node/processes'; -import { MainContext, MainThreadTaskShape, ExtHostTaskShape, IMainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { MainContext, MainThreadTaskShape, ExtHostTaskShape } from 'vs/workbench/api/common/extHost.protocol'; import * as types from 'vs/workbench/api/common/extHostTypes'; -import { ExtHostWorkspace, IExtHostWorkspaceProvider } from 'vs/workbench/api/common/extHostWorkspace'; +import { IExtHostWorkspaceProvider, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import * as vscode from 'vscode'; import { TaskDefinitionDTO, TaskExecutionDTO, TaskPresentationOptionsDTO, ProcessExecutionOptionsDTO, ProcessExecutionDTO, ShellExecutionOptionsDTO, ShellExecutionDTO, - CustomExecutionDTO, CustomExecution2DTO, TaskDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO, TaskSetDTO } from '../common/shared/tasks'; -// {{SQL CARBON EDIT}} -// import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService'; -import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { ExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; -import { ExtHostTerminalService, ExtHostTerminal } from 'vs/workbench/api/node/extHostTerminalService'; +import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService'; +import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { Schemas } from 'vs/base/common/network'; namespace TaskDefinitionDTO { export function from(value: vscode.TaskDefinition): TaskDefinitionDTO | undefined { @@ -81,7 +81,7 @@ namespace ProcessExecutionOptionsDTO { } namespace ProcessExecutionDTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO | undefined): value is ProcessExecutionDTO { + export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecution2DTO | undefined): value is ProcessExecutionDTO { if (value) { const candidate = value as ProcessExecutionDTO; return candidate && !!candidate.process; @@ -126,7 +126,7 @@ namespace ShellExecutionOptionsDTO { } namespace ShellExecutionDTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO | undefined): value is ShellExecutionDTO { + export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecution2DTO | undefined): value is ShellExecutionDTO { if (value) { const candidate = value as ShellExecutionDTO; return candidate && (!!candidate.commandLine || !!candidate.command); @@ -163,25 +163,8 @@ namespace ShellExecutionDTO { } } -namespace CustomExecutionDTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO | undefined): value is CustomExecutionDTO { - if (value) { - let candidate = value as CustomExecutionDTO; - return candidate && candidate.customExecution === 'customExecution'; - } else { - return false; - } - } - - export function from(value: vscode.CustomExecution): CustomExecutionDTO { - return { - customExecution: 'customExecution' - }; - } -} - namespace CustomExecution2DTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO | undefined): value is CustomExecution2DTO { + export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecution2DTO | undefined): value is CustomExecution2DTO { if (value) { let candidate = value as CustomExecution2DTO; return candidate && candidate.customExecution === 'customExecution2'; @@ -230,13 +213,11 @@ namespace TaskDTO { if (value === undefined || value === null) { return undefined; } - let execution: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO | undefined; + let execution: ShellExecutionDTO | ProcessExecutionDTO | CustomExecution2DTO | undefined; if (value.execution instanceof types.ProcessExecution) { execution = ProcessExecutionDTO.from(value.execution); } else if (value.execution instanceof types.ShellExecution) { execution = ShellExecutionDTO.from(value.execution); - } else if ((value).execution2 && (value).execution2 instanceof types.CustomExecution) { - execution = CustomExecutionDTO.from((value).execution2); } else if ((value).execution2 && (value).execution2 instanceof types.CustomExecution2) { execution = CustomExecution2DTO.from((value).execution2); } @@ -374,122 +355,18 @@ interface HandlerData { extension: IExtensionDescription; } -class CustomExecutionData implements IDisposable { - private static waitForDimensionsTimeoutInMs: number = 5000; - private _cancellationSource?: CancellationTokenSource; - private readonly _onTaskExecutionComplete: Emitter = new Emitter(); - private readonly _disposables = new DisposableStore(); - private terminal?: vscode.Terminal; - private terminalId?: number; - public result: number | undefined; - - constructor( - private readonly customExecution: vscode.CustomExecution, - private readonly terminalService: ExtHostTerminalService) { - } - - public dispose(): void { - this._cancellationSource = undefined; - this._disposables.dispose(); - } - - public get onTaskExecutionComplete(): Event { - return this._onTaskExecutionComplete.event; - } - - private onDidCloseTerminal(terminal: vscode.Terminal): void { - if ((this.terminal === terminal) && this._cancellationSource) { - this._cancellationSource.cancel(); - } - } - - private onDidOpenTerminal(terminal: vscode.Terminal): void { - if (!(terminal instanceof ExtHostTerminal)) { - throw new Error('How could this not be a extension host terminal?'); - } - - if (this.terminalId && terminal._id === this.terminalId) { - this.startCallback(this.terminalId); - } - } - - public async startCallback(terminalId: number): Promise { - this.terminalId = terminalId; - - // If we have already started the extension task callback, then - // do not start it again. - // It is completely valid for multiple terminals to be opened - // before the one for our task. - if (this._cancellationSource) { - return undefined; - } - - const callbackTerminals: vscode.Terminal[] = this.terminalService.terminals.filter((terminal) => terminal._id === terminalId); - - if (!callbackTerminals || callbackTerminals.length === 0) { - this._disposables.add(this.terminalService.onDidOpenTerminal(this.onDidOpenTerminal.bind(this))); - return; - } - - if (callbackTerminals.length !== 1) { - throw new Error(`Expected to only have one terminal at this point`); - } - - this.terminal = callbackTerminals[0]; - const terminalRenderer: vscode.TerminalRenderer = await this.terminalService.resolveTerminalRenderer(terminalId); - - // If we don't have the maximum dimensions yet, then we need to wait for them (but not indefinitely). - // Custom executions will expect the dimensions to be set properly before they are launched. - // BUT, due to the API contract VSCode has for terminals and dimensions, they are still responsible for - // handling cases where they are not set. - if (!terminalRenderer.maximumDimensions) { - const dimensionTimeout: Promise = new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, CustomExecutionData.waitForDimensionsTimeoutInMs); - }); - - let dimensionsRegistration: IDisposable | undefined; - const dimensionsPromise: Promise = new Promise((resolve) => { - dimensionsRegistration = terminalRenderer.onDidChangeMaximumDimensions((newDimensions) => { - resolve(); - }); - }); - - await Promise.race([dimensionTimeout, dimensionsPromise]); - if (dimensionsRegistration) { - dimensionsRegistration.dispose(); - } - } - - this._cancellationSource = new CancellationTokenSource(); - this._disposables.add(this._cancellationSource); - - this._disposables.add(this.terminalService.onDidCloseTerminal(this.onDidCloseTerminal.bind(this))); - - // Regardless of how the task completes, we are done with this custom execution task. - this.customExecution.callback(terminalRenderer, this._cancellationSource.token).then( - (success) => { - this.result = success; - this._onTaskExecutionComplete.fire(this); - }, (rejected) => { - this._onTaskExecutionComplete.fire(this); - }); - } -} - export class ExtHostTask implements ExtHostTaskShape { - private _proxy: MainThreadTaskShape; - private _workspaceProvider: IExtHostWorkspaceProvider; - private _editorService: ExtHostDocumentsAndEditors; - private _configurationService: ExtHostConfiguration; - private _terminalService: ExtHostTerminalService; + readonly _serviceBrand: any; + + private readonly _proxy: MainThreadTaskShape; + private readonly _workspaceProvider: IExtHostWorkspaceProvider; + private readonly _editorService: IExtHostDocumentsAndEditors; + private readonly _configurationService: IExtHostConfiguration; + private readonly _terminalService: IExtHostTerminalService; private _handleCounter: number; private _handlers: Map; private _taskExecutions: Map; - private _providedCustomExecutions: Map; - private _activeCustomExecutions: Map; private _providedCustomExecutions2: Map; private _activeCustomExecutions2: Map; @@ -500,12 +377,14 @@ export class ExtHostTask implements ExtHostTaskShape { private readonly _onDidTaskProcessEnded: Emitter = new Emitter(); constructor( - mainContext: IMainContext, - workspaceService: ExtHostWorkspace, - editorService: ExtHostDocumentsAndEditors, - configurationService: ExtHostConfiguration, - extHostTerminalService: ExtHostTerminalService) { - this._proxy = mainContext.getProxy(MainContext.MainThreadTask); + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @IExtHostInitDataService initData: IExtHostInitDataService, + @IExtHostWorkspace workspaceService: IExtHostWorkspace, + @IExtHostDocumentsAndEditors editorService: IExtHostDocumentsAndEditors, + @IExtHostConfiguration configurationService: IExtHostConfiguration, + @IExtHostTerminalService extHostTerminalService: IExtHostTerminalService + ) { + this._proxy = extHostRpc.getProxy(MainContext.MainThreadTask); this._workspaceProvider = workspaceService; this._editorService = editorService; this._configurationService = configurationService; @@ -513,10 +392,16 @@ export class ExtHostTask implements ExtHostTaskShape { this._handleCounter = 0; this._handlers = new Map(); this._taskExecutions = new Map(); - this._providedCustomExecutions = new Map(); - this._activeCustomExecutions = new Map(); this._providedCustomExecutions2 = new Map(); this._activeCustomExecutions2 = new Map(); + + if (initData.remote.isRemote && initData.remote.authority) { + this.registerTaskSystem(Schemas.vscodeRemote, { + scheme: Schemas.vscodeRemote, + authority: initData.remote.authority, + platform: process.platform + }); + } } public registerTaskProvider(extension: IExtensionDescription, type: string, provider: vscode.TaskProvider): vscode.Disposable { @@ -589,26 +474,7 @@ export class ExtHostTask implements ExtHostTaskShape { // Clone the custom execution to keep the original untouched. This is important for multiple runs of the same task. this._activeCustomExecutions2.set(execution.id, execution2); - await this._terminalService.attachPtyToTerminal(terminalId, await execution2.callback()); - } - - // Once a terminal is spun up for the custom execution task this event will be fired. - // At that point, we need to actually start the callback, but - // only if it hasn't already begun. - const extensionCallback: CustomExecutionData | undefined = this._providedCustomExecutions.get(execution.id); - if (extensionCallback) { - if (this._activeCustomExecutions.get(execution.id) !== undefined) { - throw new Error('We should not be trying to start the same custom task executions twice.'); - } - - this._activeCustomExecutions.set(execution.id, extensionCallback); - - const taskExecutionComplete: IDisposable = extensionCallback.onTaskExecutionComplete(() => { - this.customExecutionComplete(execution); - taskExecutionComplete.dispose(); - }); - - extensionCallback.startCallback(terminalId); + this._terminalService.attachPtyToTerminal(terminalId, await execution2.callback()); } this._onDidExecuteTask.fire({ @@ -666,7 +532,6 @@ export class ExtHostTask implements ExtHostTaskShape { // For custom execution tasks, we need to store the execution objects locally // since we obviously cannot send callback functions through the proxy. // So, clear out any existing ones. - this._providedCustomExecutions.clear(); this._providedCustomExecutions2.clear(); // Set up a list of task ID promises that we can wait on @@ -690,14 +555,11 @@ export class ExtHostTask implements ExtHostTaskShape { if (taskDTO) { taskDTOs.push(taskDTO); - if (CustomExecutionDTO.is(taskDTO.execution)) { + if (CustomExecution2DTO.is(taskDTO.execution)) { // The ID is calculated on the main thread task side, so, let's call into it here. // We need the task id's pre-computed for custom task executions because when OnDidStartTask // is invoked, we have to be able to map it back to our data. - taskIdPromises.push(this.addCustomExecution(taskDTO, task)); - } else if (CustomExecution2DTO.is(taskDTO.execution)) { taskIdPromises.push(this.addCustomExecution2(taskDTO, task)); - } } } @@ -747,10 +609,6 @@ export class ExtHostTask 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 (CustomExecutionDTO.is(resolvedTaskDTO.execution)) { - await this.addCustomExecution(resolvedTaskDTO, resolvedTask); - } - if (CustomExecution2DTO.is(resolvedTaskDTO.execution)) { await this.addCustomExecution2(resolvedTaskDTO, resolvedTask); } @@ -805,11 +663,6 @@ export class ExtHostTask implements ExtHostTaskShape { return this._handleCounter++; } - private async addCustomExecution(taskDTO: TaskDTO, task: vscode.Task2): Promise { - const taskId = await this._proxy.$createTaskId(taskDTO); - this._providedCustomExecutions.set(taskId, new CustomExecutionData((task).execution2, this._terminalService)); - } - private async addCustomExecution2(taskDTO: TaskDTO, task: vscode.Task2): Promise { const taskId = await this._proxy.$createTaskId(taskDTO); this._providedCustomExecutions2.set(taskId, (task).execution2); @@ -838,12 +691,6 @@ export class ExtHostTask implements ExtHostTaskShape { } private customExecutionComplete(execution: TaskExecutionDTO): void { - const extensionCallback: CustomExecutionData | undefined = this._activeCustomExecutions.get(execution.id); - if (extensionCallback) { - this._activeCustomExecutions.delete(execution.id); - this._proxy.$customExecutionComplete(execution.id, extensionCallback.result); - extensionCallback.dispose(); - } const extensionCallback2: vscode.CustomExecution2 | undefined = this._activeCustomExecutions2.get(execution.id); if (extensionCallback2) { this._activeCustomExecutions2.delete(execution.id); diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index af0fdf1f86..915129d569 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -10,26 +10,25 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import * as platform from 'vs/base/common/platform'; import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { Event, Emitter } from 'vs/base/common/event'; -import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IMainContext, ShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto, ITerminalDimensionsDto } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostConfiguration, ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; +import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto, ITerminalDimensionsDto } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostConfiguration, ExtHostConfigProvider, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; import { ILogService } from 'vs/platform/log/common/log'; import { EXT_HOST_CREATION_DELAY, IShellLaunchConfig, ITerminalEnvironment, ITerminalChildProcess, ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalProcess } from 'vs/workbench/contrib/terminal/node/terminalProcess'; import { timeout } from 'vs/base/common/async'; -import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; +import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService'; -import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { getSystemShell, detectAvailableShells } from 'vs/workbench/contrib/terminal/node/terminal'; import { getMainProcessParentEnv } from 'vs/workbench/contrib/terminal/node/terminalEnvironment'; -import { IDisposable } from 'vs/base/common/lifecycle'; - -const RENDERER_NO_PROCESS_ID = -1; +import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; export class BaseExtHostTerminal { - public _id: number; + public _id: number | undefined; protected _idPromise: Promise; - private _idPromiseComplete: (value: number) => any; + private _idPromiseComplete: ((value: number) => any) | undefined; private _disposed: boolean = false; private _queuedRequests: ApiRequest[] = []; @@ -71,9 +70,12 @@ export class BaseExtHostTerminal { public _runQueuedRequests(id: number): void { this._id = id; - this._idPromiseComplete(id); + if (this._idPromiseComplete) { + this._idPromiseComplete(id); + this._idPromiseComplete = undefined; + } this._queuedRequests.forEach((r) => { - r.run(this._proxy, this._id); + r.run(this._proxy, id); }); this._queuedRequests.length = 0; } @@ -82,32 +84,29 @@ export class BaseExtHostTerminal { export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Terminal { private _pidPromise: Promise; private _cols: number | undefined; - private _pidPromiseComplete: ((value: number | undefined) => any) | null; + private _pidPromiseComplete: ((value: number | undefined) => any) | undefined; private _rows: number | undefined; + /** @deprecated */ private readonly _onData = new Emitter(); + /** @deprecated */ public get onDidWriteData(): Event { // Tell the main side to start sending data if it's not already - this._idPromise.then(c => { - this._proxy.$registerOnDataListener(this._id); + this._idPromise.then(id => { + this._proxy.$registerOnDataListener(id); }); return this._onData.event; } + public isOpen: boolean = false; + constructor( proxy: MainThreadTerminalServiceShape, private _name?: string, - id?: number, - pid?: number + id?: number ) { super(proxy, id); - this._pidPromise = new Promise(c => { - if (pid === RENDERER_NO_PROCESS_ID) { - c(undefined); - } else { - this._pidPromiseComplete = c; - } - }); + this._pidPromise = new Promise(c => this._pidPromiseComplete = c); } public async create( @@ -124,10 +123,11 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi this._runQueuedRequests(terminal.id); } - public async createExtensionTerminal(): Promise { + public async createExtensionTerminal(): Promise { const terminal = await this._proxy.$createTerminal({ name: this._name, isExtensionTerminal: true }); this._name = terminal.name; this._runQueuedRequests(terminal.id); + return terminal.id; } public get name(): string { @@ -181,7 +181,7 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi // The event may fire 2 times when the panel is restored if (this._pidPromiseComplete) { this._pidPromiseComplete(processId); - this._pidPromiseComplete = null; + this._pidPromiseComplete = undefined; } else { // Recreate the promise if this is the nth processId set (e.g. reused task terminals) this._pidPromise.then(pid => { @@ -197,92 +197,14 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi } } -export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vscode.TerminalRenderer { - public get name(): string { return this._name; } - public set name(newName: string) { - this._name = newName; - this._checkDisposed(); - this._queueApiRequest(this._proxy.$terminalRendererSetName, [this._name]); - } +export class ExtHostTerminalService implements IExtHostTerminalService, ExtHostTerminalServiceShape { - private readonly _onInput = new Emitter(); - public get onDidAcceptInput(): Event { - this._checkDisposed(); - this._queueApiRequest(this._proxy.$terminalRendererRegisterOnInputListener, [this._id]); - // Tell the main side to start sending data if it's not already - // this._proxy.$terminalRendererRegisterOnDataListener(this._id); - return this._onInput && this._onInput.event; - } + readonly _serviceBrand: any; - private _dimensions: vscode.TerminalDimensions | undefined; - public get dimensions(): vscode.TerminalDimensions | undefined { return this._dimensions; } - public set dimensions(dimensions: vscode.TerminalDimensions | undefined) { - this._checkDisposed(); - this._dimensions = dimensions; - this._queueApiRequest(this._proxy.$terminalRendererSetDimensions, [dimensions]); - } - - private _maximumDimensions: vscode.TerminalDimensions | undefined; - public get maximumDimensions(): vscode.TerminalDimensions | undefined { - if (!this._maximumDimensions) { - return undefined; - } - return { - rows: this._maximumDimensions.rows, - columns: this._maximumDimensions.columns - }; - } - - private readonly _onDidChangeMaximumDimensions: Emitter = new Emitter(); - public get onDidChangeMaximumDimensions(): Event { - return this._onDidChangeMaximumDimensions && this._onDidChangeMaximumDimensions.event; - } - - public get terminal(): ExtHostTerminal { - return this._terminal; - } - - constructor( - proxy: MainThreadTerminalServiceShape, - private _name: string, - private _terminal: ExtHostTerminal, - id?: number - ) { - super(proxy, id); - - if (!id) { - this._proxy.$createTerminalRenderer(this._name).then(id => { - this._runQueuedRequests(id); - (this._terminal)._runQueuedRequests(id); - }); - } - } - - public write(data: string): void { - this._checkDisposed(); - this._queueApiRequest(this._proxy.$terminalRendererWrite, [data]); - } - - public _fireOnInput(data: string): void { - this._onInput.fire(data); - } - - public _setMaximumDimensions(columns: number, rows: number): void { - if (this._maximumDimensions && this._maximumDimensions.columns === columns && this._maximumDimensions.rows === rows) { - return; - } - const newValue = { columns, rows }; - this._maximumDimensions = newValue; - this._onDidChangeMaximumDimensions.fire(newValue); - } -} - -export class ExtHostTerminalService implements ExtHostTerminalServiceShape { private _proxy: MainThreadTerminalServiceShape; private _activeTerminal: ExtHostTerminal | undefined; private _terminals: ExtHostTerminal[] = []; private _terminalProcesses: { [id: number]: ITerminalChildProcess } = {}; - private _terminalRenderers: ExtHostTerminalRenderer[] = []; private _getTerminalPromises: { [id: number]: Promise } = {}; private _variableResolver: ExtHostVariableResolverService | undefined; private _lastActiveWorkspace: IWorkspaceFolder | undefined; @@ -301,15 +223,21 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { public get onDidChangeActiveTerminal(): Event { return this._onDidChangeActiveTerminal && this._onDidChangeActiveTerminal.event; } private readonly _onDidChangeTerminalDimensions: Emitter = new Emitter(); public get onDidChangeTerminalDimensions(): Event { return this._onDidChangeTerminalDimensions && this._onDidChangeTerminalDimensions.event; } + private readonly _onDidWriteTerminalData: Emitter; + public get onDidWriteTerminalData(): Event { return this._onDidWriteTerminalData && this._onDidWriteTerminalData.event; } constructor( - mainContext: IMainContext, - private _extHostConfiguration: ExtHostConfiguration, - private _extHostWorkspace: ExtHostWorkspace, - private _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors, - private _logService: ILogService + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @IExtHostConfiguration private _extHostConfiguration: ExtHostConfiguration, + @IExtHostWorkspace private _extHostWorkspace: ExtHostWorkspace, + @IExtHostDocumentsAndEditors private _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors, + @ILogService private _logService: ILogService ) { - this._proxy = mainContext.getProxy(MainContext.MainThreadTerminalService); + this._proxy = extHostRpc.getProxy(MainContext.MainThreadTerminalService); + this._onDidWriteTerminalData = new Emitter({ + onFirstListenerAdd: () => this._proxy.$startSendingDataEvents(), + onLastListenerRemove: () => this._proxy.$stopSendingDataEvents() + }); this._updateLastActiveWorkspace(); this._updateVariableResolver(); this._registerListeners(); @@ -332,12 +260,12 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { public createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal { const terminal = new ExtHostTerminal(this._proxy, options.name); const p = new ExtHostPseudoterminal(options.pty); - terminal.createExtensionTerminal().then(() => this._setupExtHostProcessListeners(terminal._id, p)); + terminal.createExtensionTerminal().then(id => this._setupExtHostProcessListeners(id, p)); this._terminals.push(terminal); return terminal; } - public async attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): Promise { + public attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): void { const terminal = this._getTerminalByIdEventually(id); if (!terminal) { throw new Error(`Cannot resolve terminal with id ${id} for virtual process`); @@ -346,18 +274,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { this._setupExtHostProcessListeners(id, p); } - public createTerminalRenderer(name: string): vscode.TerminalRenderer { - const terminal = new ExtHostTerminal(this._proxy, name); - terminal._setProcessId(undefined); - this._terminals.push(terminal); - - const renderer = new ExtHostTerminalRenderer(this._proxy, name, terminal); - this._terminalRenderers.push(renderer); - - return renderer; - } - - public getDefaultShell(configProvider: ExtHostConfigProvider): string { + public getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string { const fetchSetting = (key: string) => { const setting = configProvider .getConfiguration(key.substr(0, key.lastIndexOf('.'))) @@ -372,11 +289,12 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { process.env.windir, this._lastActiveWorkspace, this._variableResolver, - this._logService + this._logService, + useAutomationShell ); } - private _getDefaultShellArgs(configProvider: ExtHostConfigProvider): string[] | string { + private _getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string { const fetchSetting = (key: string) => { const setting = configProvider .getConfiguration(key.substr(0, key.lastIndexOf('.'))) @@ -384,28 +302,10 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { return this._apiInspectConfigToPlain(setting); }; - return terminalEnvironment.getDefaultShellArgs(fetchSetting, this._isWorkspaceShellAllowed, this._lastActiveWorkspace, this._variableResolver, this._logService); + return terminalEnvironment.getDefaultShellArgs(fetchSetting, this._isWorkspaceShellAllowed, useAutomationShell, this._lastActiveWorkspace, this._variableResolver, this._logService); } - public async resolveTerminalRenderer(id: number): Promise { - // Check to see if the extension host already knows about this terminal. - for (const terminalRenderer of this._terminalRenderers) { - if (terminalRenderer._id === id) { - return terminalRenderer; - } - } - - const terminal = this._getTerminalById(id); - if (!terminal) { - throw new Error(`Cannot resolve terminal renderer for terminal id ${id}`); - } - const renderer = new ExtHostTerminalRenderer(this._proxy, terminal.name, terminal, terminal._id); - this._terminalRenderers.push(renderer); - - return renderer; - } - - public $acceptActiveTerminalChanged(id: number | null): void { + public async $acceptActiveTerminalChanged(id: number | null): Promise { const original = this._activeTerminal; if (id === null) { this._activeTerminal = undefined; @@ -414,69 +314,62 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { } return; } - this.performTerminalIdAction(id, terminal => { - if (terminal) { - this._activeTerminal = terminal; - if (original !== this._activeTerminal) { - this._onDidChangeActiveTerminal.fire(this._activeTerminal); - } + const terminal = await this._getTerminalByIdEventually(id); + if (terminal) { + this._activeTerminal = terminal; + if (original !== this._activeTerminal) { + this._onDidChangeActiveTerminal.fire(this._activeTerminal); } - }); + } } - public $acceptTerminalProcessData(id: number, data: string): void { - this._getTerminalByIdEventually(id).then(terminal => { - if (terminal) { - terminal._fireOnData(data); - } - }); + /** @deprecated */ + public async $acceptTerminalProcessData(id: number, data: string): Promise { + const terminal = await this._getTerminalByIdEventually(id); + if (terminal) { + terminal._fireOnData(data); + } } - public $acceptTerminalDimensions(id: number, cols: number, rows: number): void { - this._getTerminalByIdEventually(id).then(terminal => { - if (terminal) { - if (terminal.setDimensions(cols, rows)) { - this._onDidChangeTerminalDimensions.fire({ - terminal: terminal, - dimensions: terminal.dimensions as vscode.TerminalDimensions - }); - } - } - }); + public async $acceptTerminalProcessData2(id: number, data: string): Promise { + const terminal = await this._getTerminalByIdEventually(id); + if (terminal) { + this._onDidWriteTerminalData.fire({ terminal, data }); + } } - public $acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): void { + public async $acceptTerminalDimensions(id: number, cols: number, rows: number): Promise { + const terminal = await this._getTerminalByIdEventually(id); + if (terminal) { + if (terminal.setDimensions(cols, rows)) { + this._onDidChangeTerminalDimensions.fire({ + terminal: terminal, + dimensions: terminal.dimensions as vscode.TerminalDimensions + }); + } + } + } + + public async $acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): Promise { + await this._getTerminalByIdEventually(id); + if (this._terminalProcesses[id]) { - // Virtual processes only - when virtual process resize fires it means that the + // Extension pty terminal only - when virtual process resize fires it means that the // terminal's maximum dimensions changed this._terminalProcesses[id].resize(cols, rows); - } else { - // Terminal renderer - this._getTerminalByIdEventually(id).then(() => { - // When a terminal's dimensions change, a renderer's _maximum_ dimensions change - const renderer = this._getTerminalRendererById(id); - if (renderer) { - renderer._setMaximumDimensions(cols, rows); - } - }); } } - public $acceptTerminalRendererInput(id: number, data: string): void { - const renderer = this._getTerminalRendererById(id); - if (renderer) { - renderer._fireOnInput(data); - } - } - - public $acceptTerminalTitleChange(id: number, name: string): void { + public async $acceptTerminalTitleChange(id: number, name: string): Promise { + await this._getTerminalByIdEventually(id); const extHostTerminal = this._getTerminalObjectById(this.terminals, id); if (extHostTerminal) { extHostTerminal.name = name; } } - public $acceptTerminalClosed(id: number): void { + public async $acceptTerminalClosed(id: number): Promise { + await this._getTerminalByIdEventually(id); const index = this._getTerminalObjectIndexById(this.terminals, id); if (index !== null) { const terminal = this._terminals.splice(index, 1)[0]; @@ -489,20 +382,25 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { if (index !== null) { // The terminal has already been created (via createTerminal*), only fire the event this._onDidOpenTerminal.fire(this.terminals[index]); + this.terminals[index].isOpen = true; return; } - const renderer = this._getTerminalRendererById(id); - const terminal = new ExtHostTerminal(this._proxy, name, id, renderer ? RENDERER_NO_PROCESS_ID : undefined); + const terminal = new ExtHostTerminal(this._proxy, name, id); this._terminals.push(terminal); this._onDidOpenTerminal.fire(terminal); + terminal.isOpen = true; } - public $acceptTerminalProcessId(id: number, processId: number): void { - this.performTerminalIdAction(id, terminal => terminal._setProcessId(processId)); + public async $acceptTerminalProcessId(id: number, processId: number): Promise { + const terminal = await this._getTerminalByIdEventually(id); + if (terminal) { + terminal._setProcessId(processId); + } } public performTerminalIdAction(id: number, callback: (terminal: ExtHostTerminal) => void): void { + // TODO: Use await this._getTerminalByIdEventually(id); let terminal = this._getTerminalById(id); if (terminal) { callback(terminal); @@ -551,7 +449,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { this._variableResolver = new ExtHostVariableResolverService(workspaceFolders || [], this._extHostDocumentsAndEditors, configProvider); } - public async $spawnExtHostProcess(id: number, shellLaunchConfigDto: ShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise { + public async $spawnExtHostProcess(id: number, shellLaunchConfigDto: IShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise { const shellLaunchConfig: IShellLaunchConfig = { name: shellLaunchConfigDto.name, executable: shellLaunchConfigDto.executable, @@ -564,8 +462,8 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux'); const configProvider = await this._extHostConfiguration.getConfigProvider(); if (!shellLaunchConfig.executable) { - shellLaunchConfig.executable = this.getDefaultShell(configProvider); - shellLaunchConfig.args = this._getDefaultShellArgs(configProvider); + shellLaunchConfig.executable = this.getDefaultShell(false, configProvider); + shellLaunchConfig.args = this._getDefaultShellArgs(false, configProvider); } else { if (this._variableResolver) { shellLaunchConfig.executable = this._variableResolver.resolve(this._lastActiveWorkspace, shellLaunchConfig.executable); @@ -625,7 +523,27 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { public async $startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise { // Make sure the ExtHostTerminal exists so onDidOpenTerminal has fired before we call // Pseudoterminal.start - await this._getTerminalByIdEventually(id); + const terminal = await this._getTerminalByIdEventually(id); + if (!terminal) { + return; + } + + // Wait for onDidOpenTerminal to fire + let openPromise: Promise; + if (terminal.isOpen) { + openPromise = Promise.resolve(); + } else { + openPromise = new Promise(r => { + // Ensure open is called after onDidOpenTerminal + const listener = this.onDidOpenTerminal(async e => { + if (e === terminal) { + listener.dispose(); + r(); + } + }); + }); + } + await openPromise; // Processes should be initialized here for normal virtual process terminals, however for // tasks they are responsible for attaching the virtual process to a terminal so this @@ -686,11 +604,11 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { return detectAvailableShells(); } - public async $requestDefaultShellAndArgs(): Promise { + public async $requestDefaultShellAndArgs(useAutomationShell: boolean): Promise { const configProvider = await this._extHostConfiguration.getConfigProvider(); return Promise.resolve({ - shell: this.getDefaultShell(configProvider), - args: this._getDefaultShellArgs(configProvider) + shell: this.getDefaultShell(useAutomationShell, configProvider), + args: this._getDefaultShellArgs(useAutomationShell, configProvider) }); } @@ -702,7 +620,8 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { this._proxy.$sendProcessExit(id, exitCode); } - private _getTerminalByIdEventually(id: number, retries: number = 5): Promise { + // TODO: This could be improved by using a single promise and resolve it when the terminal is ready + private _getTerminalByIdEventually(id: number, retries: number = 5): Promise { if (!this._getTerminalPromises[id]) { this._getTerminalPromises[id] = this._createGetTerminalPromise(id, retries); } else { @@ -735,16 +654,12 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { return this._getTerminalObjectById(this._terminals, id); } - private _getTerminalRendererById(id: number): ExtHostTerminalRenderer | null { - return this._getTerminalObjectById(this._terminalRenderers, id); - } - - private _getTerminalObjectById(array: T[], id: number): T | null { + private _getTerminalObjectById(array: T[], id: number): T | null { const index = this._getTerminalObjectIndexById(array, id); return index !== null ? array[index] : null; } - private _getTerminalObjectIndexById(array: T[], id: number): number | null { + private _getTerminalObjectIndexById(array: T[], id: number): number | null { let index: number | null = null; array.some((item, i) => { const thisId = item._id; @@ -777,9 +692,6 @@ class ApiRequest { } class ExtHostPseudoterminal implements ITerminalChildProcess { - private _queuedEvents: (IQueuedEvent | IQueuedEvent | IQueuedEvent<{ pid: number, cwd: string }> | IQueuedEvent)[] = []; - private _queueDisposables: IDisposable[] | undefined; - private readonly _onProcessData = new Emitter(); public readonly onProcessData: Event = this._onProcessData.event; private readonly _onProcessExit = new Emitter(); @@ -791,23 +703,10 @@ class ExtHostPseudoterminal implements ITerminalChildProcess { private readonly _onProcessOverrideDimensions = new Emitter(); public get onProcessOverrideDimensions(): Event { return this._onProcessOverrideDimensions.event; } - constructor( - private readonly _pty: vscode.Pseudoterminal - ) { - this._queueDisposables = []; - this._queueDisposables.push(this._pty.onDidWrite(e => this._queuedEvents.push({ emitter: this._onProcessData, data: e }))); - if (this._pty.onDidClose) { - this._queueDisposables.push(this._pty.onDidClose(e => this._queuedEvents.push({ emitter: this._onProcessExit, data: 0 }))); - } - if (this._pty.onDidOverrideDimensions) { - this._queueDisposables.push(this._pty.onDidOverrideDimensions(e => this._queuedEvents.push({ emitter: this._onProcessOverrideDimensions, data: e ? { cols: e.columns, rows: e.rows } : undefined }))); - } - } + constructor(private readonly _pty: vscode.Pseudoterminal) { } shutdown(): void { - if (this._pty.close) { - this._pty.close(); - } + this._pty.close(); } input(data: string): void { @@ -835,12 +734,7 @@ class ExtHostPseudoterminal implements ITerminalChildProcess { } startSendingEvents(initialDimensions: ITerminalDimensionsDto | undefined): void { - // Flush all buffered events - this._queuedEvents.forEach(e => (e.emitter.fire)(e.data)); - this._queuedEvents = []; - this._queueDisposables = undefined; - - // Attach the real listeners + // Attach the listeners this._pty.onDidWrite(e => this._onProcessData.fire(e)); if (this._pty.onDidClose) { this._pty.onDidClose(e => this._onProcessExit.fire(0)); @@ -849,13 +743,7 @@ class ExtHostPseudoterminal implements ITerminalChildProcess { this._pty.onDidOverrideDimensions(e => this._onProcessOverrideDimensions.fire(e ? { cols: e.columns, rows: e.rows } : undefined)); // {{SQL CARBON EDIT}} strict-null-checks } - if (this._pty.open) { - this._pty.open(initialDimensions); - } + this._pty.open(initialDimensions ? initialDimensions : undefined); } } -interface IQueuedEvent { - emitter: Emitter; - data: T; -} diff --git a/src/vs/workbench/browser/actions.ts b/src/vs/workbench/browser/actions.ts index c0171828b4..c8200d1876 100644 --- a/src/vs/workbench/browser/actions.ts +++ b/src/vs/workbench/browser/actions.ts @@ -156,7 +156,7 @@ export interface IActionBarRegistry { class ActionBarRegistry implements IActionBarRegistry { private readonly actionBarContributorConstructors: { scope: string; ctor: IConstructorSignature0; }[] = []; private readonly actionBarContributorInstances: Map = new Map(); - private instantiationService: IInstantiationService; + private instantiationService!: IInstantiationService; start(accessor: ServicesAccessor): void { this.instantiationService = accessor.get(IInstantiationService); diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index f95b111dde..a9b1a8a358 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -3,6 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./media/screencast'; + import { Action } from 'vs/base/common/actions'; import { IWindowService } from 'vs/platform/windows/common/windows'; import * as nls from 'vs/nls'; @@ -11,6 +13,7 @@ import { domEvent } from 'vs/base/browser/event'; import { Event } from 'vs/base/common/event'; import { IDisposable, toDisposable, dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { getDomNodePagePosition, createStyleSheet, createCSSRule, append, $ } from 'vs/base/browser/dom'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Context } from 'vs/platform/contextkey/browser/contextKeyService'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -20,6 +23,9 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { clamp } from 'vs/base/common/numbers'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; export class InspectContextKeysAction extends Action { @@ -96,7 +102,8 @@ export class ToggleScreencastModeAction extends Action { id: string, label: string, @IKeybindingService private readonly keybindingService: IKeybindingService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(id, label); } @@ -108,26 +115,17 @@ export class ToggleScreencastModeAction extends Action { return; } - const container = this.layoutService.getWorkbenchElement(); + const disposables = new DisposableStore(); - const mouseMarker = append(container, $('div')); - mouseMarker.style.position = 'absolute'; - mouseMarker.style.border = '2px solid red'; - mouseMarker.style.borderRadius = '20px'; - mouseMarker.style.width = '20px'; - mouseMarker.style.height = '20px'; - mouseMarker.style.top = '0'; - mouseMarker.style.left = '0'; - mouseMarker.style.zIndex = '100000'; - mouseMarker.style.content = ' '; - mouseMarker.style.pointerEvents = 'none'; - mouseMarker.style.display = 'none'; + const container = this.layoutService.getWorkbenchElement(); + const mouseMarker = append(container, $('.screencast-mouse')); + disposables.add(toDisposable(() => mouseMarker.remove())); const onMouseDown = domEvent(container, 'mousedown', true); const onMouseUp = domEvent(container, 'mouseup', true); const onMouseMove = domEvent(container, 'mousemove', true); - const mouseListener = onMouseDown(e => { + disposables.add(onMouseDown(e => { mouseMarker.style.top = `${e.clientY - 10}px`; mouseMarker.style.left = `${e.clientX - 10}px`; mouseMarker.style.display = 'block'; @@ -141,56 +139,59 @@ export class ToggleScreencastModeAction extends Action { mouseMarker.style.display = 'none'; mouseMoveListener.dispose(); }); - }); + })); - const keyboardMarker = append(container, $('div')); - keyboardMarker.style.position = 'absolute'; - keyboardMarker.style.backgroundColor = 'rgba(0, 0, 0 ,0.5)'; - keyboardMarker.style.width = '100%'; - keyboardMarker.style.height = '100px'; - keyboardMarker.style.bottom = '20%'; - keyboardMarker.style.left = '0'; - keyboardMarker.style.zIndex = '100000'; - keyboardMarker.style.pointerEvents = 'none'; - keyboardMarker.style.color = 'white'; - keyboardMarker.style.lineHeight = '100px'; - keyboardMarker.style.textAlign = 'center'; - keyboardMarker.style.fontSize = '56px'; - keyboardMarker.style.display = 'none'; + const keyboardMarker = append(container, $('.screencast-keyboard')); + disposables.add(toDisposable(() => keyboardMarker.remove())); + + const updateKeyboardMarker = () => { + keyboardMarker.style.bottom = `${clamp(this.configurationService.getValue('screencastMode.verticalOffset') || 0, 0, 90)}%`; + }; + + updateKeyboardMarker(); + disposables.add(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('screencastMode.verticalOffset')) { + updateKeyboardMarker(); + } + })); const onKeyDown = domEvent(container, 'keydown', true); let keyboardTimeout: IDisposable = Disposable.None; + let length = 0; - const keyboardListener = onKeyDown(e => { + disposables.add(onKeyDown(e => { keyboardTimeout.dispose(); const event = new StandardKeyboardEvent(e); - const keybinding = this.keybindingService.resolveKeyboardEvent(event); - const label = keybinding.getLabel(); + const shortcut = this.keybindingService.softDispatch(event, event.target); - if (!event.ctrlKey && !event.altKey && !event.metaKey && !event.shiftKey && this.keybindingService.mightProducePrintableCharacter(event) && label) { - keyboardMarker.textContent += ' ' + label; - } else { - keyboardMarker.textContent = label; + if (shortcut || !this.configurationService.getValue('screencastMode.onlyKeyboardShortcuts')) { + if ( + event.ctrlKey || event.altKey || event.metaKey || event.shiftKey + || length > 20 + || event.keyCode === KeyCode.Backspace || event.keyCode === KeyCode.Escape + ) { + keyboardMarker.innerHTML = ''; + length = 0; + } + + const keybinding = this.keybindingService.resolveKeyboardEvent(event); + const label = keybinding.getLabel(); + const key = $('span.key', {}, label || ''); + length++; + append(keyboardMarker, key); } - keyboardMarker.style.display = 'block'; - const promise = timeout(800); keyboardTimeout = toDisposable(() => promise.cancel()); promise.then(() => { keyboardMarker.textContent = ''; - keyboardMarker.style.display = 'none'; + length = 0; }); - }); + })); - ToggleScreencastModeAction.disposable = toDisposable(() => { - mouseListener.dispose(); - keyboardListener.dispose(); - mouseMarker.remove(); - keyboardMarker.remove(); - }); + ToggleScreencastModeAction.disposable = disposables; } } @@ -215,8 +216,35 @@ export class LogStorageAction extends Action { } } +// --- Actions Registration + const developerCategory = nls.localize('developer', "Developer"); const registry = Registry.as(Extensions.WorkbenchActions); registry.registerWorkbenchAction(new SyncActionDescriptor(InspectContextKeysAction, InspectContextKeysAction.ID, InspectContextKeysAction.LABEL), 'Developer: Inspect Context Keys', developerCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleScreencastModeAction, ToggleScreencastModeAction.ID, ToggleScreencastModeAction.LABEL), 'Developer: Toggle Screencast Mode', developerCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(LogStorageAction, LogStorageAction.ID, LogStorageAction.LABEL), 'Developer: Log Storage Database Contents', developerCategory); \ No newline at end of file +registry.registerWorkbenchAction(new SyncActionDescriptor(LogStorageAction, LogStorageAction.ID, LogStorageAction.LABEL), 'Developer: Log Storage Database Contents', developerCategory); + +// --- Menu Registration + +// Screencast Mode +const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); +configurationRegistry.registerConfiguration({ + id: 'screencastMode', + order: 9, + title: nls.localize('screencastModeConfigurationTitle', "Screencast Mode"), + type: 'object', + properties: { + 'screencastMode.verticalOffset': { + type: 'number', + default: 20, + minimum: 0, + maximum: 90, + description: nls.localize('screencastMode.location.verticalPosition', "Controls the vertical offset of the screencast mode overlay from the bottom as a percentage of the workbench height.") + }, + 'screencastMode.onlyKeyboardShortcuts': { + type: 'boolean', + description: nls.localize('screencastMode.onlyKeyboardShortcuts', "Only show keyboard shortcuts in Screencast Mode."), + default: false + } + } +}); diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index 66b265a95f..e98782f936 100644 --- a/src/vs/workbench/browser/actions/listCommands.ts +++ b/src/vs/workbench/browser/actions/listCommands.ts @@ -320,6 +320,38 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'list.focusParent', + weight: KeybindingWeight.WorkbenchContrib, + when: WorkbenchListFocusContextKey, + handler: (accessor) => { + const focused = accessor.get(IListService).lastFocusedList; + + if (!focused || focused instanceof List || focused instanceof PagedList) { + return; + } + + if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { + const tree = focused; + const focusedElements = tree.getFocus(); + if (focusedElements.length === 0) { + return; + } + const focus = focusedElements[0]; + const parent = tree.getParentElement(focus); + if (parent) { + const fakeKeyboardEvent = new KeyboardEvent('keydown'); + tree.setFocus([parent], fakeKeyboardEvent); + tree.reveal(parent); + } + } else { + const tree = focused; + tree.focusParent({ origin: 'keyboard' }); + } + } +}); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'list.expand', weight: KeybindingWeight.WorkbenchContrib, @@ -749,11 +781,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ if (focused instanceof List || focused instanceof PagedList) { const list = focused; - if (list.getSelection().length > 0) { - list.setSelection([]); - } else if (list.getFocus().length > 0) { - list.setFocus([]); - } + list.setSelection([]); + list.setFocus([]); } // ObjectTree @@ -761,22 +790,16 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const list = focused; const fakeKeyboardEvent = new KeyboardEvent('keydown'); - if (list.getSelection().length > 0) { - list.setSelection([], fakeKeyboardEvent); - } else if (list.getFocus().length > 0) { - list.setFocus([], fakeKeyboardEvent); - } + list.setSelection([], fakeKeyboardEvent); + list.setFocus([], fakeKeyboardEvent); } // Tree else if (focused) { const tree = focused; - if (tree.getSelection().length) { - tree.clearSelection({ origin: 'keyboard' }); - } else if (tree.getFocus()) { - tree.clearFocus({ origin: 'keyboard' }); - } + tree.clearSelection({ origin: 'keyboard' }); + tree.clearFocus({ origin: 'keyboard' }); } } }); diff --git a/src/vs/workbench/browser/actions/media/screencast.css b/src/vs/workbench/browser/actions/media/screencast.css new file mode 100644 index 0000000000..0391ee490e --- /dev/null +++ b/src/vs/workbench/browser/actions/media/screencast.css @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-workbench .screencast-mouse { + position: absolute; + border: 2px solid red; + border-radius: 20px; + width: 20px; + height: 20px; + top: 0; + left: 0; + z-index: 100000; + content: ' '; + pointer-events: none; + display: none; +} + +.monaco-workbench .screencast-keyboard { + position: absolute; + background-color: rgba(0, 0, 0 ,0.5); + width: 100%; + height: 100px; + bottom: 20%; + left: 0; + z-index: 100000; + pointer-events: none; + color: #eee; + line-height: 100px; + text-align: center; + font-size: 56px; + transition: opacity 0.3s ease-out; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.monaco-workbench .screencast-keyboard:empty { + opacity: 0; +} + +.monaco-workbench .screencast-keyboard > .key { + padding: 0 8px; + box-shadow: inset 0 -3px 0 hsla(0,0%,73%,.4); + margin-right: 6px; + border: 1px solid hsla(0,0%,80%,.4); + border-radius: 5px; + background-color: rgba(255, 255, 255, 0.05); +} diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index 90093bd432..283d570fe7 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -7,11 +7,11 @@ import 'vs/css!./media/actions'; import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; -import { IWindowService, IURIToOpen } from 'vs/platform/windows/common/windows'; +import { IWindowService, IURIToOpen, IWindowsService } from 'vs/platform/windows/common/windows'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IsFullscreenContext, IsDevelopmentContext } from 'vs/workbench/browser/contextkeys'; +import { IsFullscreenContext, IsDevelopmentContext, IsMacNativeContext } from 'vs/workbench/browser/contextkeys'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -226,6 +226,24 @@ export class ReloadWindowAction extends Action { } } +export class ShowAboutDialogAction extends Action { + + static readonly ID = 'workbench.action.showAboutDialog'; + static readonly LABEL = nls.localize('about', "About"); + + constructor( + id: string, + label: string, + @IWindowsService private readonly windowsService: IWindowsService + ) { + super(id, label); + } + + run(): Promise { + return this.windowsService.openAboutDialog(); + } +} + const registry = Registry.as(Extensions.WorkbenchActions); // --- Actions Registration @@ -240,6 +258,9 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleFullScreenAction const developerCategory = nls.localize('developer', "Developer"); registry.registerWorkbenchAction(new SyncActionDescriptor(ReloadWindowAction, ReloadWindowAction.ID, ReloadWindowAction.LABEL), 'Developer: Reload Window', developerCategory); +const helpCategory = nls.localize('help', "Help"); +registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAboutDialogAction, ShowAboutDialogAction.ID, ShowAboutDialogAction.LABEL), `Help: About`, helpCategory); + // --- Commands/Keybindings Registration const recentFilesPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExpr.has(inRecentFilesPickerContextKey)); @@ -297,4 +318,14 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { toggled: IsFullscreenContext }, order: 1 -}); \ No newline at end of file +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: 'z_about', + command: { + id: ShowAboutDialogAction.ID, + title: nls.localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About") + }, + order: 1, + when: IsMacNativeContext.toNegated() +}); diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index 75588cff8a..7cefca23a2 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -11,7 +11,7 @@ import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/p import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ICommandService, ICommandHandler } from 'vs/platform/commands/common/commands'; +import { ICommandService, ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL, PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -20,6 +20,9 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { ITextFileService, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { toResource } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; +import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class OpenFileAction extends Action { @@ -305,3 +308,19 @@ export class DuplicateWorkspaceInNewWindowAction extends Action { return this.windowService.openWindow([{ workspaceUri: newWorkspace.configPath }], { forceNewWindow: true }); } } + +// --- Menu Registration + +const workspacesCategory = nls.localize('workspaces', "Workspaces"); + +CommandsRegistry.registerCommand(OpenWorkspaceConfigFileAction.ID, serviceAccessor => { + serviceAccessor.get(IInstantiationService).createInstance(OpenWorkspaceConfigFileAction, OpenWorkspaceConfigFileAction.ID, OpenWorkspaceConfigFileAction.LABEL).run(); +}); + +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: OpenWorkspaceConfigFileAction.ID, + title: { value: `${workspacesCategory}: ${OpenWorkspaceConfigFileAction.LABEL}`, original: 'Workspaces: Open Workspace Configuration File' }, + }, + when: WorkbenchStateContext.isEqualTo('workspace') +}); diff --git a/src/vs/workbench/browser/composite.ts b/src/vs/workbench/browser/composite.ts index 3c00b4c1ce..8893bdbde3 100644 --- a/src/vs/workbench/browser/composite.ts +++ b/src/vs/workbench/browser/composite.ts @@ -35,7 +35,7 @@ export abstract class Composite extends Component implements IComposite { private readonly _onDidChangeVisibility: Emitter = this._register(new Emitter()); readonly onDidChangeVisibility: Event = this._onDidChangeVisibility.event; - private _onDidFocus: Emitter; + private _onDidFocus!: Emitter; get onDidFocus(): Event { if (!this._onDidFocus) { this.registerFocusTrackEvents(); @@ -50,7 +50,7 @@ export abstract class Composite extends Component implements IComposite { } } - private _onDidBlur: Emitter; + private _onDidBlur!: Emitter; get onDidBlur(): Event { if (!this._onDidBlur) { this.registerFocusTrackEvents(); @@ -68,10 +68,10 @@ export abstract class Composite extends Component implements IComposite { this._register(focusTracker.onDidBlur(() => this._onDidBlur.fire())); } - protected actionRunner: IActionRunner; + protected actionRunner: IActionRunner | undefined; private visible: boolean; - private parent: HTMLElement; + private parent!: HTMLElement; constructor( id: string, diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index 55edb94332..28f273ce5f 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys'; import { IWindowsConfiguration } from 'vs/platform/windows/common/windows'; -import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext } from 'vs/workbench/common/editor'; +import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -52,17 +52,20 @@ export class WorkbenchContextKeysHandler extends Disposable { private inputFocusedContext: IContextKey; private activeEditorContext: IContextKey; + + private activeEditorGroupEmpty: IContextKey; + private activeEditorGroupIndex: IContextKey; + private activeEditorGroupLast: IContextKey; + private multipleEditorGroupsContext: IContextKey; + private editorsVisibleContext: IContextKey; private textCompareEditorVisibleContext: IContextKey; private textCompareEditorActiveContext: IContextKey; - private activeEditorGroupEmpty: IContextKey; - private multipleEditorGroupsContext: IContextKey; private splitEditorsVerticallyContext: IContextKey; private workbenchStateContext: IContextKey; private workspaceFolderCountContext: IContextKey; - private inZenModeContext: IContextKey; private isFullscreenContext: IContextKey; private isCenteredLayoutContext: IContextKey; @@ -90,8 +93,10 @@ export class WorkbenchContextKeysHandler extends Disposable { this._register(this.editorService.onDidActiveEditorChange(() => this.updateEditorContextKeys())); this._register(this.editorService.onDidVisibleEditorsChange(() => this.updateEditorContextKeys())); + this._register(this.editorGroupService.onDidAddGroup(() => this.updateEditorContextKeys())); this._register(this.editorGroupService.onDidRemoveGroup(() => this.updateEditorContextKeys())); + this._register(this.editorGroupService.onDidGroupIndexChange(() => this.updateEditorContextKeys())); this._register(addDisposableListener(window, EventType.FOCUS_IN, () => this.updateInputContextKeys(), true)); @@ -141,6 +146,8 @@ export class WorkbenchContextKeysHandler extends Disposable { this.textCompareEditorVisibleContext = TextCompareEditorVisibleContext.bindTo(this.contextKeyService); this.textCompareEditorActiveContext = TextCompareEditorActiveContext.bindTo(this.contextKeyService); this.activeEditorGroupEmpty = ActiveEditorGroupEmptyContext.bindTo(this.contextKeyService); + this.activeEditorGroupIndex = ActiveEditorGroupIndexContext.bindTo(this.contextKeyService); + this.activeEditorGroupLast = ActiveEditorGroupLastContext.bindTo(this.contextKeyService); this.multipleEditorGroupsContext = MultipleEditorGroupsContext.bindTo(this.contextKeyService); // Inputs @@ -176,6 +183,7 @@ export class WorkbenchContextKeysHandler extends Disposable { } private updateEditorContextKeys(): void { + const activeGroup = this.editorGroupService.activeGroup; const activeControl = this.editorService.activeControl; const visibleEditors = this.editorService.visibleControls; @@ -194,12 +202,16 @@ export class WorkbenchContextKeysHandler extends Disposable { this.activeEditorGroupEmpty.reset(); } - if (this.editorGroupService.count > 1) { + const groupCount = this.editorGroupService.count; + if (groupCount > 1) { this.multipleEditorGroupsContext.set(true); } else { this.multipleEditorGroupsContext.reset(); } + this.activeEditorGroupIndex.set(activeGroup.index); + this.activeEditorGroupLast.set(activeGroup.index === groupCount - 1); + if (activeControl) { this.activeEditorContext.set(activeControl.getId()); } else { diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index d8bc7d2038..2a00a80c24 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -27,13 +27,15 @@ import { IWindowService, MenuBarVisibility, getTitleBarStyle } from 'vs/platform import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { Sizing, Direction, Grid, SerializableGrid, ISerializableView, ISerializedGrid, GridBranchNode, GridLeafNode, isGridBranchNode } from 'vs/base/browser/ui/grid/grid'; +import { Grid, SerializableGrid, ISerializableView, ISerializedGrid, Orientation, ISerializedNode, ISerializedLeafNode, Direction } from 'vs/base/browser/ui/grid/grid'; import { WorkbenchLegacyLayout } from 'vs/workbench/browser/legacyLayout'; import { IDimension } from 'vs/platform/layout/browser/layoutService'; import { Part } from 'vs/workbench/browser/part'; import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; import { IFileService } from 'vs/platform/files/common/files'; +import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { coalesce } from 'vs/base/common/arrays'; enum Settings { MENUBAR_VISIBLE = 'window.menuBarVisibility', @@ -44,25 +46,36 @@ enum Settings { PANEL_POSITION = 'workbench.panel.defaultLocation', ZEN_MODE_RESTORE = 'zenMode.restore', - } enum Storage { SIDEBAR_HIDDEN = 'workbench.sidebar.hidden', + SIDEBAR_SIZE = 'workbench.sidebar.size', PANEL_HIDDEN = 'workbench.panel.hidden', PANEL_POSITION = 'workbench.panel.location', + PANEL_SIZE = 'workbench.panel.size', PANEL_SIZE_BEFORE_MAXIMIZED = 'workbench.panel.sizeBeforeMaximized', ZEN_MODE_ENABLED = 'workbench.zenmode.active', CENTERED_LAYOUT_ENABLED = 'workbench.centerededitorlayout.active', - GRID_LAYOUT = 'workbench.grid.layout' + GRID_LAYOUT = 'workbench.grid.layout', + GRID_WIDTH = 'workbench.grid.width', + GRID_HEIGHT = 'workbench.grid.height' +} + +enum Classes { + SIDEBAR_HIDDEN = 'nosidebar', + EDITOR_HIDDEN = 'noeditorarea', + PANEL_HIDDEN = 'nopanel', + STATUSBAR_HIDDEN = 'nostatusbar', + FULLSCREEN = 'fullscreen' } export abstract class Layout extends Disposable implements IWorkbenchLayoutService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private readonly _onTitleBarVisibilityChange: Emitter = this._register(new Emitter()); readonly onTitleBarVisibilityChange: Event = this._onTitleBarVisibilityChange.event; @@ -232,6 +245,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (this.state.fullscreen && (this.state.menuBar.visibility === 'toggle' || this.state.menuBar.visibility === 'default')) { this._onTitleBarVisibilityChange.fire(); + + if (this.workbenchGrid instanceof SerializableGrid) { + this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); + } + this.layout(); } } @@ -242,9 +260,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Apply as CSS class if (this.state.fullscreen) { - addClass(this.container, 'fullscreen'); + addClass(this.container, Classes.FULLSCREEN); } else { - removeClass(this.container, 'fullscreen'); + removeClass(this.container, Classes.FULLSCREEN); if (this.state.zenMode.transitionedToFullScreen && this.state.zenMode.active) { this.toggleZenMode(); @@ -254,6 +272,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Changing fullscreen state of the window has an impact on custom title bar visibility, so we need to update if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { this._onTitleBarVisibilityChange.fire(); + + if (this.workbenchGrid instanceof SerializableGrid) { + this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); + } + this.layout(); // handle title bar when fullscreen changes } @@ -297,11 +320,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const activityBar = this.getPart(Parts.ACTIVITYBAR_PART); const sideBar = this.getPart(Parts.SIDEBAR_PART); const wasHidden = this.state.sideBar.hidden; - - if (this.state.sideBar.hidden) { - this.setSideBarHidden(false, true /* Skip Layout */); - } - const newPositionValue = (position === Position.LEFT) ? 'left' : 'right'; const oldPositionValue = (this.state.sideBar.position === Position.LEFT) ? 'left' : 'right'; this.state.sideBar.position = position; @@ -322,11 +340,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.state.sideBar.width = this.workbenchGrid.getViewSize(this.sideBarPartView).width; } - this.workbenchGrid.removeView(this.sideBarPartView); - this.workbenchGrid.removeView(this.activityBarPartView); - - if (!this.state.panel.hidden && this.state.panel.position === Position.BOTTOM) { - this.workbenchGrid.removeView(this.panelPartView); + if (position === Position.LEFT) { + this.workbenchGrid.moveViewTo(this.activityBarPartView, [1, 0]); + this.workbenchGrid.moveViewTo(this.sideBarPartView, [1, 1]); + } else { + this.workbenchGrid.moveViewTo(this.sideBarPartView, [1, 4]); + this.workbenchGrid.moveViewTo(this.activityBarPartView, [1, 4]); } this.layout(); @@ -578,7 +597,15 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.state.zenMode.active = !this.state.zenMode.active; this.state.zenMode.transitionDisposables.clear(); - const setLineNumbers = (lineNumbers: any) => this.editorService.visibleTextEditorWidgets.forEach(editor => editor.updateOptions({ lineNumbers })); + const setLineNumbers = (lineNumbers?: any) => this.editorService.visibleTextEditorWidgets.forEach(editor => { + // To properly reset line numbers we need to read the configuration for each editor respecting it's uri. + if (!lineNumbers && isCodeEditor(editor) && editor.hasModel()) { + const model = editor.getModel(); + this.configurationService.getValue('editor.lineNumbers', { resource: model.uri }); + } else { + editor.updateOptions({ lineNumbers }); + } + }); // Check if zen mode transitioned to full screen and if now we are out of zen mode // -> we need to go out of full screen (same goes for the centered editor layout) @@ -641,7 +668,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.centerEditorLayout(false, true); } - setLineNumbers(this.configurationService.getValue('editor.lineNumbers')); + setLineNumbers(); // Status bar and activity bar visibility come from settings -> update their visibility. this.doUpdateLayoutConfiguration(true); @@ -684,16 +711,19 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Adjust CSS if (hidden) { - addClass(this.container, 'nostatusbar'); + addClass(this.container, Classes.STATUSBAR_HIDDEN); } else { - removeClass(this.container, 'nostatusbar'); + removeClass(this.container, Classes.STATUSBAR_HIDDEN); + } + + // Propagate to grid + if (this.workbenchGrid instanceof Grid) { + this.workbenchGrid.setViewVisible(this.statusBarPartView, !hidden); } // Layout if (!skipLayout) { - if (this.workbenchGrid instanceof Grid) { - this.layout(); - } else { + if (!(this.workbenchGrid instanceof Grid)) { this.workbenchGrid.layout(); } } @@ -717,64 +747,21 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.panelPartView = panelPart; this.statusBarPartView = statusBar; - let workbenchGrid: SerializableGrid | undefined; + const viewMap = { + [Parts.ACTIVITYBAR_PART]: this.activityBarPartView, + [Parts.TITLEBAR_PART]: this.titleBarPartView, + [Parts.EDITOR_PART]: this.editorPartView, + [Parts.PANEL_PART]: this.panelPartView, + [Parts.SIDEBAR_PART]: this.sideBarPartView, + [Parts.STATUSBAR_PART]: this.statusBarPartView + }; - const savedGrid = this.storageService.get(Storage.GRID_LAYOUT, StorageScope.GLOBAL, undefined); - if (savedGrid) { - const parsedGrid: ISerializedGrid = JSON.parse(savedGrid); - - const fromJSON = (serializedPart: { type: Parts } | null) => { - if (serializedPart && serializedPart.type) { - switch (serializedPart.type) { - case Parts.ACTIVITYBAR_PART: - return this.activityBarPartView; - case Parts.TITLEBAR_PART: - return this.titleBarPartView; - case Parts.EDITOR_PART: - return this.editorPartView; - case Parts.PANEL_PART: - return this.panelPartView; - case Parts.SIDEBAR_PART: - return this.sideBarPartView; - case Parts.STATUSBAR_PART: - return this.statusBarPartView; - default: - return this.editorPartView; - } - } else { - return this.editorPartView; - } - }; - - try { - workbenchGrid = SerializableGrid.deserialize(parsedGrid, { fromJSON }, { proportionalLayout: false }); - - const root = workbenchGrid.getViews(); - const titleBarSection = root.children[0]; - - if (isGridBranchNode(titleBarSection) || titleBarSection.view !== this.titleBarPartView) { - throw new Error('Bad grid'); - } - - const middleSection = root.children[1] as GridBranchNode; - const sideBarPosition = (middleSection.children[0] as GridLeafNode).view === this.activityBarPartView ? Position.LEFT : Position.RIGHT; - if (sideBarPosition !== this.state.sideBar.position) { - throw new Error('Bad Grid'); - } - - const panelPosition = isGridBranchNode(middleSection.children[2]) || isGridBranchNode(middleSection.children[0]) ? Position.BOTTOM : Position.RIGHT; - if (panelPosition !== this.state.panel.position) { - throw new Error('Bad Grid'); - } - } catch (err) { - workbenchGrid = undefined; - console.error(err); - } - } - - if (!workbenchGrid) { - workbenchGrid = new SerializableGrid(this.editorPartView, { proportionalLayout: false }); - } + const fromJSON = ({ type }: { type: Parts }) => viewMap[type]; + const workbenchGrid = SerializableGrid.deserialize( + this.createGridDescriptor(), + { fromJSON }, + { proportionalLayout: false } + ); this.container.prepend(workbenchGrid.element); this.workbenchGrid = workbenchGrid; @@ -791,15 +778,24 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.setEditorHidden(!visible, true); })); - this._register(this.lifecycleService.onBeforeShutdown(beforeShutdownEvent => { - beforeShutdownEvent.veto(new Promise((resolve) => { - const grid = this.workbenchGrid as SerializableGrid; - const serializedGrid = grid.serialize(); + this._register(this.storageService.onWillSaveState(() => { + const grid = this.workbenchGrid as SerializableGrid; - this.storageService.store(Storage.GRID_LAYOUT, JSON.stringify(serializedGrid), StorageScope.GLOBAL); + const sideBarSize = this.state.sideBar.hidden + ? grid.getViewCachedVisibleSize(this.sideBarPartView) + : grid.getViewSize(this.sideBarPartView).width; - resolve(); - })); + this.storageService.store(Storage.SIDEBAR_SIZE, sideBarSize, StorageScope.GLOBAL); + + const panelSize = this.state.panel.hidden + ? grid.getViewCachedVisibleSize(this.panelPartView) + : (this.state.panel.position === Position.BOTTOM ? grid.getViewSize(this.panelPartView).height : grid.getViewSize(this.panelPartView).width); + + this.storageService.store(Storage.PANEL_SIZE, panelSize, StorageScope.GLOBAL); + + const gridSize = grid.getViewSize(); + this.storageService.store(Storage.GRID_WIDTH, gridSize.width, StorageScope.GLOBAL); + this.storageService.store(Storage.GRID_HEIGHT, gridSize.height, StorageScope.GLOBAL); })); } else { this.workbenchGrid = instantiationService.createInstance( @@ -828,9 +824,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Layout the grid widget this.workbenchGrid.layout(this._dimension.width, this._dimension.height); - - // Layout grid views - this.layoutGrid(); } else { this.workbenchGrid.layout(options); } @@ -840,95 +833,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } - private layoutGrid(): void { - if (!(this.workbenchGrid instanceof Grid)) { - return; - } - - let panelInGrid = this.workbenchGrid.hasView(this.panelPartView); - let sidebarInGrid = this.workbenchGrid.hasView(this.sideBarPartView); - let activityBarInGrid = this.workbenchGrid.hasView(this.activityBarPartView); - let statusBarInGrid = this.workbenchGrid.hasView(this.statusBarPartView); - let titlebarInGrid = this.workbenchGrid.hasView(this.titleBarPartView); - - // Add parts to grid - if (!statusBarInGrid) { - this.workbenchGrid.addView(this.statusBarPartView, Sizing.Split, this.editorPartView, Direction.Down); - statusBarInGrid = true; - } - - if (!titlebarInGrid) { - this.workbenchGrid.addView(this.titleBarPartView, Sizing.Split, this.editorPartView, Direction.Up); - - titlebarInGrid = true; - } - - if (!activityBarInGrid) { - this.workbenchGrid.addView(this.activityBarPartView, Sizing.Split, panelInGrid && this.state.sideBar.position === this.state.panel.position ? this.panelPartView : this.editorPartView, this.state.sideBar.position === Position.RIGHT ? Direction.Right : Direction.Left); - activityBarInGrid = true; - } - - if (!sidebarInGrid) { - this.workbenchGrid.addView(this.sideBarPartView, this.state.sideBar.width !== undefined ? this.state.sideBar.width : Sizing.Split, this.activityBarPartView, this.state.sideBar.position === Position.LEFT ? Direction.Right : Direction.Left); - sidebarInGrid = true; - } - - if (!panelInGrid) { - this.workbenchGrid.addView(this.panelPartView, Sizing.Split, this.editorPartView, this.state.panel.position === Position.BOTTOM ? Direction.Down : Direction.Right); - panelInGrid = true; - } - - // Hide parts - if (this.state.panel.hidden) { - this.workbenchGrid.setViewVisible(this.panelPartView, false); - } - - if (this.state.statusBar.hidden) { - this.workbenchGrid.setViewVisible(this.statusBarPartView, false); - } - - if (titlebarInGrid && !this.isVisible(Parts.TITLEBAR_PART)) { - this.workbenchGrid.setViewVisible(this.titleBarPartView, false); - } - - if (this.state.activityBar.hidden) { - this.workbenchGrid.setViewVisible(this.activityBarPartView, false); - } - - if (this.state.sideBar.hidden) { - this.workbenchGrid.setViewVisible(this.sideBarPartView, false); - } - - if (this.state.editor.hidden) { - this.workbenchGrid.setViewVisible(this.editorPartView, false); - } - - // Show visible parts - if (!this.state.editor.hidden) { - this.workbenchGrid.setViewVisible(this.editorPartView, true); - } - - if (!this.state.statusBar.hidden) { - this.workbenchGrid.setViewVisible(this.statusBarPartView, true); - } - - if (this.isVisible(Parts.TITLEBAR_PART)) { - this.workbenchGrid.setViewVisible(this.titleBarPartView, true); - } - - if (!this.state.activityBar.hidden) { - this.workbenchGrid.setViewVisible(this.activityBarPartView, true); - } - - if (!this.state.sideBar.hidden) { - this.workbenchGrid.setViewVisible(this.sideBarPartView, true); - } - - if (!this.state.panel.hidden) { - this.workbenchGrid.setViewVisible(this.panelPartView, true); - } - } - isEditorLayoutCentered(): boolean { return this.state.editor.centered; } @@ -1019,41 +923,60 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi setActivityBarHidden(hidden: boolean, skipLayout?: boolean): void { this.state.activityBar.hidden = hidden; + // Propagate to grid + if (this.workbenchGrid instanceof Grid) { + this.workbenchGrid.setViewVisible(this.activityBarPartView, !hidden); + } + // Layout if (!skipLayout) { - if (this.workbenchGrid instanceof Grid) { - this.layout(); - } else { + if (!(this.workbenchGrid instanceof Grid)) { this.workbenchGrid.layout(); } } } setEditorHidden(hidden: boolean, skipLayout?: boolean): void { - if (!(this.workbenchGrid instanceof Grid) || hidden === this.state.editor.hidden) { + if (!(this.workbenchGrid instanceof Grid)) { return; } this.state.editor.hidden = hidden; - // The editor and the panel cannot be hidden at the same time - if (this.state.editor.hidden && this.state.panel.hidden) { - this.setPanelHidden(false, true); + // Adjust CSS + if (hidden) { + addClass(this.container, Classes.EDITOR_HIDDEN); + } else { + removeClass(this.container, Classes.EDITOR_HIDDEN); } - if (!skipLayout) { - this.layout(); + // Propagate to grid + this.workbenchGrid.setViewVisible(this.editorPartView, !hidden); + + // The editor and panel cannot be hidden at the same time + if (hidden && this.state.panel.hidden) { + this.setPanelHidden(false, true); } } + getLayoutClasses(): string[] { + return coalesce([ + this.state.sideBar.hidden ? Classes.SIDEBAR_HIDDEN : undefined, + this.state.editor.hidden ? Classes.EDITOR_HIDDEN : undefined, + this.state.panel.hidden ? Classes.PANEL_HIDDEN : undefined, + this.state.statusBar.hidden ? Classes.STATUSBAR_HIDDEN : undefined, + this.state.fullscreen ? Classes.FULLSCREEN : undefined + ]); + } + setSideBarHidden(hidden: boolean, skipLayout?: boolean): void { this.state.sideBar.hidden = hidden; // Adjust CSS if (hidden) { - addClass(this.container, 'nosidebar'); + addClass(this.container, Classes.SIDEBAR_HIDDEN); } else { - removeClass(this.container, 'nosidebar'); + removeClass(this.container, Classes.SIDEBAR_HIDDEN); } // If sidebar becomes hidden, also hide the current active Viewlet if any @@ -1080,6 +1003,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } + // Propagate to grid + if (this.workbenchGrid instanceof Grid) { + this.workbenchGrid.setViewVisible(this.sideBarPartView, !hidden); + } + // Remember in settings const defaultHidden = this.contextService.getWorkbenchState() === WorkbenchState.EMPTY; if (hidden !== defaultHidden) { @@ -1090,9 +1018,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Layout if (!skipLayout) { - if (this.workbenchGrid instanceof Grid) { - this.layout(); - } else { + if (!(this.workbenchGrid instanceof Grid)) { this.workbenchGrid.layout(); } } @@ -1103,9 +1029,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Adjust CSS if (hidden) { - addClass(this.container, 'nopanel'); + addClass(this.container, Classes.PANEL_HIDDEN); } else { - removeClass(this.container, 'nopanel'); + removeClass(this.container, Classes.PANEL_HIDDEN); } // If panel part becomes hidden, also hide the current active panel if any @@ -1123,6 +1049,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } + // Propagate to grid + if (this.workbenchGrid instanceof Grid) { + this.workbenchGrid.setViewVisible(this.panelPartView, !hidden); + } + // Remember in settings if (!hidden) { this.storageService.store(Storage.PANEL_HIDDEN, 'false', StorageScope.WORKSPACE); @@ -1137,9 +1068,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Layout if (!skipLayout) { - if (this.workbenchGrid instanceof Grid) { - this.layout(); - } else { + if (!(this.workbenchGrid instanceof Grid)) { this.workbenchGrid.layout(); } } @@ -1147,33 +1076,15 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi toggleMaximizedPanel(): void { if (this.workbenchGrid instanceof Grid) { - const curSize = this.workbenchGrid.getViewSize(this.panelPartView); - const size = { ...curSize }; - + const size = this.workbenchGrid.getViewSize(this.panelPartView); if (!this.isPanelMaximized()) { - if (this.state.panel.position === Position.BOTTOM) { - size.height = this.panelPartView.maximumHeight; - this.state.panel.sizeBeforeMaximize = curSize.height; - } else { - size.width = this.panelPartView.maximumWidth; - this.state.panel.sizeBeforeMaximize = curSize.width; - } - + this.state.panel.sizeBeforeMaximize = this.state.panel.position === Position.BOTTOM ? size.height : size.width; this.storageService.store(Storage.PANEL_SIZE_BEFORE_MAXIMIZED, this.state.panel.sizeBeforeMaximize, StorageScope.GLOBAL); + this.setEditorHidden(true); } else { - if (this.state.panel.position === Position.BOTTOM) { - size.height = this.state.panel.sizeBeforeMaximize; - } else { - size.width = this.state.panel.sizeBeforeMaximize; - } - - // Unhide the editor if needed - if (this.state.editor.hidden) { - this.setEditorHidden(false); - } + this.setEditorHidden(false); + this.workbenchGrid.resizeView(this.panelPartView, { width: this.state.panel.position === Position.BOTTOM ? size.width : this.state.panel.sizeBeforeMaximize, height: this.state.panel.position === Position.BOTTOM ? this.state.panel.sizeBeforeMaximize : size.height }); } - - this.workbenchGrid.resizeView(this.panelPartView, size); } else { this.workbenchGrid.layout({ toggleMaximizedPanel: true, source: Parts.PANEL_PART }); } @@ -1185,16 +1096,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } if (this.workbenchGrid instanceof Grid) { - try { - // The panel is maximum when the editor is minimum - if (this.state.panel.position === Position.BOTTOM) { - return this.workbenchGrid.getViewSize(this.editorPartView).height <= this.editorPartView.minimumHeight; - } else { - return this.workbenchGrid.getViewSize(this.editorPartView).width <= this.editorPartView.minimumWidth; - } - } catch (e) { - return false; - } + return this.state.editor.hidden; } else { return this.workbenchGrid.isPanelMaximized(); } @@ -1211,8 +1113,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Layout if (!skipLayout) { if (this.workbenchGrid instanceof Grid) { - const dimensions = getClientArea(this.parent); - this.workbenchGrid.layout(dimensions.width, dimensions.height); + this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); } else { this.workbenchGrid.layout(); } @@ -1228,13 +1129,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return this.state.panel.position; } - setPanelPosition(position: Position): void { - const panelPart = this.getPart(Parts.PANEL_PART); - + setPanelPosition(position: Position.BOTTOM | Position.RIGHT): void { if (this.state.panel.hidden) { - this.setPanelHidden(false, true /* Skip Layout */); + this.setPanelHidden(false); } + const panelPart = this.getPart(Parts.PANEL_PART); const newPositionValue = (position === Position.BOTTOM) ? 'bottom' : 'right'; const oldPositionValue = (this.state.panel.position === Position.BOTTOM) ? 'bottom' : 'right'; this.state.panel.position = position; @@ -1258,8 +1158,17 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Layout if (this.workbenchGrid instanceof Grid) { - this.workbenchGrid.removeView(this.panelPartView); - this.layout(); + const size = this.workbenchGrid.getViewSize(this.panelPartView); + const sideBarSize = this.workbenchGrid.getViewSize(this.sideBarPartView); + + if (position === Position.BOTTOM) { + this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.height : size.width, this.editorPartView, Direction.Down); + } else { + this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.width : size.height, this.editorPartView, Direction.Right); + } + + // Reset sidebar to original size before shifting the panel + this.workbenchGrid.resizeView(this.sideBarPartView, sideBarSize); } else { this.workbenchGrid.layout(); } @@ -1267,9 +1176,89 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._onPanelPositionChange.fire(positionToString(this.state.panel.position)); } + private createGridDescriptor(): ISerializedGrid { + const width = this.storageService.getNumber(Storage.GRID_WIDTH, StorageScope.GLOBAL, 600); + const height = this.storageService.getNumber(Storage.GRID_HEIGHT, StorageScope.GLOBAL, 400); + const sideBarSize = this.storageService.getNumber(Storage.SIDEBAR_SIZE, StorageScope.GLOBAL, 300); + const panelSize = this.storageService.getNumber(Storage.PANEL_SIZE, StorageScope.GLOBAL, 300); + + const titleBarHeight = this.titleBarPartView.minimumHeight; + const statusBarHeight = this.statusBarPartView.minimumHeight; + const activityBarWidth = this.activityBarPartView.minimumWidth; + const middleSectionHeight = height - titleBarHeight - statusBarHeight; + const editorSectionWidth = width - (this.state.activityBar.hidden ? 0 : activityBarWidth) - (this.state.sideBar.hidden ? 0 : sideBarSize); + + const activityBarNode: ISerializedLeafNode = { + type: 'leaf', + data: { type: Parts.ACTIVITYBAR_PART }, + size: activityBarWidth, + visible: !this.state.activityBar.hidden + }; + + const sideBarNode: ISerializedLeafNode = { + type: 'leaf', + data: { type: Parts.SIDEBAR_PART }, + size: sideBarSize, + visible: !this.state.sideBar.hidden + }; + + const editorNode: ISerializedLeafNode = { + type: 'leaf', + data: { type: Parts.EDITOR_PART }, + size: this.state.panel.position === Position.BOTTOM ? middleSectionHeight - (this.state.panel.hidden ? 0 : panelSize) : editorSectionWidth - (this.state.panel.hidden ? 0 : panelSize) + }; + + const panelNode: ISerializedLeafNode = { + type: 'leaf', + data: { type: Parts.PANEL_PART }, + size: panelSize, + visible: !this.state.panel.hidden + }; + + const editorSectionNode: ISerializedNode[] = this.state.panel.position === Position.BOTTOM + ? [{ type: 'branch', data: [editorNode, panelNode], size: editorSectionWidth }] + : [editorNode, panelNode]; + + const middleSection: ISerializedNode[] = this.state.sideBar.position === Position.LEFT + ? [activityBarNode, sideBarNode, ...editorSectionNode] + : [...editorSectionNode, sideBarNode, activityBarNode]; + + const result: ISerializedGrid = { + root: { + type: 'branch', + size: width, + data: [ + { + type: 'leaf', + data: { type: Parts.TITLEBAR_PART }, + size: titleBarHeight, + visible: this.isVisible(Parts.TITLEBAR_PART) + }, + { + type: 'branch', + data: middleSection, + size: middleSectionHeight + }, + { + type: 'leaf', + data: { type: Parts.STATUSBAR_PART }, + size: statusBarHeight, + visible: !this.state.statusBar.hidden + } + ] + }, + orientation: Orientation.VERTICAL, + width, + height + }; + + return result; + } + dispose(): void { super.dispose(); this.disposed = true; } } + diff --git a/src/vs/workbench/browser/panel.ts b/src/vs/workbench/browser/panel.ts index c7423dcdfa..0a8e59e957 100644 --- a/src/vs/workbench/browser/panel.ts +++ b/src/vs/workbench/browser/panel.ts @@ -25,7 +25,7 @@ export class PanelDescriptor extends CompositeDescriptor { } export class PanelRegistry extends CompositeRegistry { - private defaultPanelId: string; + private defaultPanelId!: string; /** * Registers a panel to the platform. diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index bf0d9a2242..4e96f874ee 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -48,7 +48,7 @@ interface ICachedViewlet { export class ActivitybarPart extends Part implements IActivityBarService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private static readonly ACTION_HEIGHT = 48; private static readonly PINNED_VIEWLETS = 'workbench.activity.pinnedViewlets'; diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 047f9e111b..ebedec7d5b 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -46,10 +46,10 @@ export interface ICompositeBarOptions { export class CompositeBar extends Widget implements ICompositeBar { - private dimension: Dimension; + private dimension: Dimension | undefined; - private compositeSwitcherBar: ActionBar; - private compositeOverflowAction: CompositeOverflowActivityAction | null; + private compositeSwitcherBar!: ActionBar; + private compositeOverflowAction: CompositeOverflowActivityAction | undefined; private compositeOverflowActionViewItem: CompositeOverflowActivityActionViewItem | undefined; private model: CompositeBarModel; @@ -343,7 +343,7 @@ export class CompositeBar extends Widget implements ICompositeBar { this.compositeSwitcherBar.pull(this.compositeSwitcherBar.length() - 1); this.compositeOverflowAction.dispose(); - this.compositeOverflowAction = null; + this.compositeOverflowAction = undefined; if (this.compositeOverflowActionViewItem) { this.compositeOverflowActionViewItem.dispose(); @@ -460,7 +460,7 @@ interface ICompositeBarModelItem extends ICompositeBarItem { class CompositeBarModel { - private _items: ICompositeBarModelItem[]; + private _items: ICompositeBarModelItem[] = []; private readonly options: ICompositeBarOptions; activeItem?: ICompositeBarModelItem; diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 28948354f3..e27b603e54 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -124,12 +124,12 @@ export interface IActivityActionViewItemOptions extends IBaseActionViewItemOptio } export class ActivityActionViewItem extends BaseActionViewItem { - protected container: HTMLElement; - protected label: HTMLElement; - protected badge: HTMLElement; - protected options: IActivityActionViewItemOptions; + protected container!: HTMLElement; + protected label!: HTMLElement; + protected badge!: HTMLElement; + protected options!: IActivityActionViewItemOptions; - private badgeContent: HTMLElement; + private badgeContent: HTMLElement | undefined; private readonly badgeDisposable = this._register(new MutableDisposable()); private mouseUpTimeout: any; @@ -347,7 +347,7 @@ export class CompositeOverflowActivityAction extends ActivityAction { } export class CompositeOverflowActivityActionViewItem extends ActivityActionViewItem { - private actions: Action[]; + private actions: Action[] | undefined; constructor( action: ActivityAction, @@ -371,8 +371,8 @@ export class CompositeOverflowActivityActionViewItem extends ActivityActionViewI this.contextMenuService.showContextMenu({ getAnchor: () => this.element!, - getActions: () => this.actions, - onHide: () => dispose(this.actions) + getActions: () => this.actions!, + onHide: () => dispose(this.actions!) }); } @@ -402,7 +402,9 @@ export class CompositeOverflowActivityActionViewItem extends ActivityActionViewI dispose(): void { super.dispose(); - this.actions = dispose(this.actions); + if (this.actions) { + this.actions = dispose(this.actions); + } } } @@ -431,7 +433,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem { private static manageExtensionAction: ManageExtensionAction; - private compositeActivity: IActivity | null; + private compositeActivity: IActivity | undefined; private compositeTransfer: LocalSelectionTransfer; constructor( @@ -454,7 +456,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem { CompositeActionViewItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction); } - this._register(compositeActivityAction.onDidChangeActivity(() => { this.compositeActivity = null; this.updateActivity(); }, this)); + this._register(compositeActivityAction.onDidChangeActivity(() => { this.compositeActivity = undefined; this.updateActivity(); }, this)); } protected get activity(): IActivity { diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 0031831894..2251b71fa9 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -58,18 +58,18 @@ export abstract class CompositePart extends Part { protected readonly onDidCompositeOpen = this._register(new Emitter<{ composite: IComposite, focus: boolean }>()); protected readonly onDidCompositeClose = this._register(new Emitter()); - protected toolBar: ToolBar; + protected toolBar!: ToolBar; private mapCompositeToCompositeContainer = new Map(); private mapActionsBindingToComposite = new Map void>(); private activeComposite: Composite | null; private lastActiveCompositeId: string; private instantiatedCompositeItems: Map; - private titleLabel: ICompositeTitleLabel; - private progressBar: ProgressBar; - private contentAreaSize: Dimension; + private titleLabel!: ICompositeTitleLabel; + private progressBar!: ProgressBar; + private contentAreaSize: Dimension | undefined; private readonly telemetryActionsListener = this._register(new MutableDisposable()); - private currentCompositeOpenToken: string; + private currentCompositeOpenToken: string | undefined; constructor( private notificationService: INotificationService, diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts index 7375d1074b..b00ef19f94 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts @@ -29,7 +29,7 @@ export interface IBreadcrumbsService { export class BreadcrumbsService implements IBreadcrumbsService { - _serviceBrand: ServiceIdentifier; + _serviceBrand: any; private readonly _map = new Map(); @@ -55,8 +55,8 @@ registerSingleton(IBreadcrumbsService, BreadcrumbsService, true); export abstract class BreadcrumbsConfig { - name: string; - onDidChange: Event; + abstract get name(): string; + abstract get onDidChange(): Event; abstract getValue(overrides?: IConfigurationOverrides): T; abstract updateValue(value: T, overrides?: IConfigurationOverrides): Promise; diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 12c25f368f..f78caa1eb4 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -56,11 +56,11 @@ export abstract class BreadcrumbsPicker { protected readonly _disposables = new DisposableStore(); protected readonly _domNode: HTMLDivElement; - protected _arrow: HTMLDivElement; - protected _treeContainer: HTMLDivElement; - protected _tree: Tree; + protected _arrow!: HTMLDivElement; + protected _treeContainer!: HTMLDivElement; + protected _tree!: Tree; protected _fakeEvent = new UIEvent('fakeEvent'); - protected _layoutInfo: ILayoutInfo; + protected _layoutInfo!: ILayoutInfo; private readonly _onDidPickElement = new Emitter(); readonly onDidPickElement: Event = this._onDidPickElement.event; diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 770a4a5a6a..67df837b2b 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -36,7 +36,7 @@ import { SplitEditorUpAction, SplitEditorDownAction, MoveEditorToLeftGroupAction, MoveEditorToRightGroupAction, MoveEditorToAboveGroupAction, MoveEditorToBelowGroupAction, CloseAllEditorGroupsAction, JoinAllGroupsAction, FocusLeftGroup, FocusAboveGroup, FocusRightGroup, FocusBelowGroup, EditorLayoutSingleAction, EditorLayoutTwoColumnsAction, EditorLayoutThreeColumnsAction, EditorLayoutTwoByTwoGridAction, EditorLayoutTwoRowsAction, EditorLayoutThreeRowsAction, EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoRowsRightAction, NewEditorGroupLeftAction, NewEditorGroupRightAction, - NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction + NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction, ToggleGroupSizesAction } from 'vs/workbench/browser/parts/editor/editorActions'; import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -339,6 +339,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(JoinTwoGroupsAction, J registry.registerWorkbenchAction(new SyncActionDescriptor(JoinAllGroupsAction, JoinAllGroupsAction.ID, JoinAllGroupsAction.LABEL), 'View: Join All Editor Groups', category); registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateBetweenGroupsAction, NavigateBetweenGroupsAction.ID, NavigateBetweenGroupsAction.LABEL), 'View: Navigate Between Editor Groups', category); registry.registerWorkbenchAction(new SyncActionDescriptor(ResetGroupSizesAction, ResetGroupSizesAction.ID, ResetGroupSizesAction.LABEL), 'View: Reset Editor Group Sizes', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleGroupSizesAction, ToggleGroupSizesAction.ID, ToggleGroupSizesAction.LABEL), 'View: Toggle Editor Group Sizes', category); registry.registerWorkbenchAction(new SyncActionDescriptor(MaximizeGroupAction, MaximizeGroupAction.ID, MaximizeGroupAction.LABEL), 'View: Maximize Editor Group and Hide Side Bar', category); registry.registerWorkbenchAction(new SyncActionDescriptor(MinimizeOtherGroupsAction, MinimizeOtherGroupsAction.ID, MinimizeOtherGroupsAction.LABEL), 'View: Maximize Editor Group', category); registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorLeftInGroupAction, MoveEditorLeftInGroupAction.ID, MoveEditorLeftInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageUp, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow) } }), 'View: Move Editor Left', category); diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 4f8343ec67..21b70abd80 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -81,12 +81,15 @@ export interface IEditorOpeningEvent extends IEditorIdentifier { } export interface IEditorGroupsAccessor { + readonly groups: IEditorGroupView[]; readonly activeGroup: IEditorGroupView; readonly partOptions: IEditorPartOptions; readonly onDidEditorPartOptionsChange: Event; + readonly onDidVisibilityChange: Event; + getGroup(identifier: GroupIdentifier): IEditorGroupView | undefined; getGroups(order: GroupsOrder): IEditorGroupView[]; @@ -106,6 +109,9 @@ export interface IEditorGroupView extends IDisposable, ISerializableView, IEdito readonly whenRestored: Promise; readonly disposed: boolean; + readonly isEmpty: boolean; + readonly isMinimized: boolean; + readonly onDidFocus: Event; readonly onWillDispose: Event; readonly onWillOpenEditor: Event; @@ -113,9 +119,10 @@ export interface IEditorGroupView extends IDisposable, ISerializableView, IEdito readonly onWillCloseEditor: Event; readonly onDidCloseEditor: Event; - isEmpty(): boolean; setActive(isActive: boolean): void; - setLabel(label: string): void; + + notifyIndexChanged(newIndex: number): void; + relayout(): void; } diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 18ce45c76f..043f6a062b 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -880,6 +880,22 @@ export class ResetGroupSizesAction extends Action { } } +export class ToggleGroupSizesAction extends Action { + + static readonly ID = 'workbench.action.toggleEditorWidths'; + static readonly LABEL = nls.localize('toggleEditorWidths', "Toggle Editor Group Sizes"); + + constructor(id: string, label: string, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService) { + super(id, label); + } + + run(): Promise { + this.editorGroupService.arrangeGroups(GroupsArrangement.TOGGLE); + + return Promise.resolve(false); + } +} + export class MaximizeGroupAction extends Action { static readonly ID = 'workbench.action.maximizeEditor'; diff --git a/src/vs/workbench/browser/parts/editor/editorControl.ts b/src/vs/workbench/browser/parts/editor/editorControl.ts index 109ca8e3a3..21ec7001e9 100644 --- a/src/vs/workbench/browser/parts/editor/editorControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorControl.ts @@ -221,6 +221,12 @@ export class EditorControl extends Disposable { } } + setVisible(visible: boolean): void { + if (this._activeControl) { + this._activeControl.setVisible(visible, this.groupView); + } + } + layout(dimension: Dimension): void { this.dimension = dimension; diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 7fde637ffc..c2ec00218b 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -409,7 +409,7 @@ class DropOverlay extends Themable { } private getOverlayOffsetHeight(): number { - if (!this.groupView.isEmpty() && this.accessor.partOptions.showTabs) { + if (!this.groupView.isEmpty && this.accessor.partOptions.showTabs) { return EDITOR_TITLE_HEIGHT; // show overlay below title if group shows tabs } diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index f67e2b0c67..da7adbac9d 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -58,16 +58,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region factory - static createNew(accessor: IEditorGroupsAccessor, label: string, instantiationService: IInstantiationService): IEditorGroupView { - return instantiationService.createInstance(EditorGroupView, accessor, null, label); + static createNew(accessor: IEditorGroupsAccessor, index: number, instantiationService: IInstantiationService): IEditorGroupView { + return instantiationService.createInstance(EditorGroupView, accessor, null, index); } - static createFromSerialized(serialized: ISerializedEditorGroup, accessor: IEditorGroupsAccessor, label: string, instantiationService: IInstantiationService): IEditorGroupView { - return instantiationService.createInstance(EditorGroupView, accessor, serialized, label); + static createFromSerialized(serialized: ISerializedEditorGroup, accessor: IEditorGroupsAccessor, index: number, instantiationService: IInstantiationService): IEditorGroupView { + return instantiationService.createInstance(EditorGroupView, accessor, serialized, index); } - static createCopy(copyFrom: IEditorGroupView, accessor: IEditorGroupsAccessor, label: string, instantiationService: IInstantiationService): IEditorGroupView { - return instantiationService.createInstance(EditorGroupView, accessor, copyFrom, label); + static createCopy(copyFrom: IEditorGroupView, accessor: IEditorGroupsAccessor, index: number, instantiationService: IInstantiationService): IEditorGroupView { + return instantiationService.createInstance(EditorGroupView, accessor, copyFrom, index); } //#endregion @@ -123,7 +123,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { constructor( private accessor: IEditorGroupsAccessor, from: IEditorGroupView | ISerializedEditorGroup, - private _label: string, + private _index: number, @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, @@ -256,7 +256,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Open new file via doubleclick on empty container this._register(addDisposableListener(this.element, EventType.DBLCLICK, e => { - if (this.isEmpty()) { + if (this.isEmpty) { EventHelper.stop(e); // {{SQL CARBON EDIT}} this.commandService.executeCommand(GlobalNewUntitledFileAction.ID).then(undefined, err => this.notificationService.warn(err)); @@ -265,7 +265,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Close empty editor group via middle mouse click this._register(addDisposableListener(this.element, EventType.MOUSE_UP, e => { - if (this.isEmpty() && e.button === 1 /* Middle Button */) { + if (this.isEmpty && e.button === 1 /* Middle Button */) { EventHelper.stop(e); this.accessor.removeGroup(this); @@ -314,7 +314,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } private onShowContainerContextMenu(menu: IMenu, e?: MouseEvent): void { - if (!this.isEmpty()) { + if (!this.isEmpty) { return; // only for empty editor groups } @@ -345,7 +345,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Container const containerFocusTracker = this._register(trackFocus(this.element)); this._register(containerFocusTracker.onDidFocus(() => { - if (this.isEmpty()) { + if (this.isEmpty) { this._onDidFocus.fire(); // only when empty to prevent accident focus } })); @@ -387,7 +387,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private updateContainer(): void { // Empty Container: add some empty container attributes - if (this.isEmpty()) { + if (this.isEmpty) { addClass(this.element, 'empty'); this.element.tabIndex = 0; this.element.setAttribute('aria-label', localize('emptyEditorGroup', "{0} (empty)", this.label)); @@ -474,6 +474,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Option Changes this._register(this.accessor.onDidEditorPartOptionsChange(e => this.onDidEditorPartOptionsChange(e))); + + // Visibility + this._register(this.accessor.onDidVisibilityChange(e => this.onDidVisibilityChange(e))); } private onDidEditorPin(editor: EditorInput): void { @@ -597,8 +600,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Title control Switch between showing tabs <=> not showing tabs if (event.oldPartOptions.showTabs !== event.newPartOptions.showTabs) { - this.createTitleAreaControl(); + // Recreate and layout control + this.createTitleAreaControl(); + this.layoutTitleAreaControl(); + + // Ensure to show active editor if any if (this._group.activeEditor) { this.titleAreaControl.openEditor(this._group.activeEditor); } @@ -641,6 +648,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_LABEL, editor }); } + private onDidVisibilityChange(visible: boolean): void { + + // Forward to editor control + this.editorControl.setVisible(visible); + } + //#endregion //region IEditorGroupView @@ -649,8 +662,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this._group; } + get index(): number { + return this._index; + } + get label(): string { - return this._label; + return localize('groupLabel', "Group {0}", this._index + 1); } get disposed(): boolean { @@ -661,10 +678,22 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this._whenRestored; } - setLabel(label: string): void { - if (this._label !== label) { - this._label = label; - this._onDidGroupChange.fire({ kind: GroupChangeKind.GROUP_LABEL }); + get isEmpty(): boolean { + return this._group.count === 0; + } + + get isMinimized(): boolean { + if (!this.dimension) { + return false; + } + + return this.dimension.width === this.minimumWidth || this.dimension.height === this.minimumHeight; + } + + notifyIndexChanged(newIndex: number): void { + if (this._index !== newIndex) { + this._index = newIndex; + this._onDidGroupChange.fire({ kind: GroupChangeKind.GROUP_INDEX }); } } @@ -685,10 +714,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._onDidGroupChange.fire({ kind: GroupChangeKind.GROUP_ACTIVE }); } - isEmpty(): boolean { - return this._group.count === 0; - } - //#endregion //#region IEditorGroup @@ -1213,7 +1238,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region closeEditors() async closeEditors(args: EditorInput[] | ICloseEditorsFilter, options?: ICloseEditorOptions): Promise { - if (this.isEmpty()) { + if (this.isEmpty) { return; } @@ -1285,7 +1310,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region closeAllEditors() async closeAllEditors(): Promise { - if (this.isEmpty()) { + if (this.isEmpty) { // If the group is empty and the request is to close all editors, we still close // the editor group is the related setting to close empty groups is enabled for @@ -1397,7 +1422,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region Themable protected updateStyles(): void { - const isEmpty = this.isEmpty(); + const isEmpty = this.isEmpty; // Container if (isEmpty) { @@ -1446,10 +1471,14 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.editorContainer.style.height = `calc(100% - ${this.titleAreaControl.getPreferredHeight()}px)`; // Forward to controls - this.titleAreaControl.layout(new Dimension(this.dimension.width, this.titleAreaControl.getPreferredHeight())); + this.layoutTitleAreaControl(); this.editorControl.layout(new Dimension(this.dimension.width, this.dimension.height - this.titleAreaControl.getPreferredHeight())); } + private layoutTitleAreaControl(): void { + this.titleAreaControl.layout(new Dimension(this.dimension.width, this.titleAreaControl.getPreferredHeight())); + } + relayout(): void { if (this.dimension) { const { width, height } = this.dimension; @@ -1469,7 +1498,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._onWillDispose.fire(); this.titleAreaControl.dispose(); - // this.editorControl = null; super.dispose(); } diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index ae5dafea7f..885b2e6a80 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -24,7 +24,6 @@ import { assign } from 'vs/base/common/objects'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; import { EditorDropTarget } from 'vs/workbench/browser/parts/editor/editorDropTarget'; -import { localize } from 'vs/nls'; import { Color } from 'vs/base/common/color'; import { CenteredViewLayout } from 'vs/base/browser/ui/centered/centeredViewLayout'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -82,7 +81,7 @@ class GridWidgetView implements IView { export class EditorPart extends Part implements IEditorGroupsService, IEditorGroupsAccessor { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private static readonly EDITOR_PART_UI_STATE_STORAGE_KEY = 'editorpart.state'; private static readonly EDITOR_PART_CENTERED_VIEW_STORAGE_KEY = 'editorpart.centeredview'; @@ -95,6 +94,9 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro private readonly _onDidActiveGroupChange: Emitter = this._register(new Emitter()); readonly onDidActiveGroupChange: Event = this._onDidActiveGroupChange.event; + private readonly _onDidGroupIndexChange: Emitter = this._register(new Emitter()); + readonly onDidGroupIndexChange: Event = this._onDidGroupIndexChange.event; + private readonly _onDidActivateGroup: Emitter = this._register(new Emitter()); readonly onDidActivateGroup: Event = this._onDidActivateGroup.event; @@ -344,15 +346,36 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro return; // we have not been created yet } - // Even all group sizes - if (arrangement === GroupsArrangement.EVEN) { - this.gridWidget.distributeViewSizes(); + switch (arrangement) { + case GroupsArrangement.EVEN: + this.gridWidget.distributeViewSizes(); + break; + case GroupsArrangement.MINIMIZE_OTHERS: + this.gridWidget.maximizeViewSize(this.activeGroup); + break; + case GroupsArrangement.TOGGLE: + if (this.isGroupMaximized(this.activeGroup)) { + this.arrangeGroups(GroupsArrangement.EVEN); + } else { + this.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS); + } + + break; + } + } + + private isGroupMaximized(targetGroup: IEditorGroupView): boolean { + for (const group of this.groups) { + if (group === targetGroup) { + continue; // ignore target group + } + + if (!group.isMinimized) { + return false; // target cannot be maximized if one group is not minimized + } } - // Maximize the current active group - else { - this.gridWidget.maximizeViewSize(this.activeGroup); - } + return true; } setGroupOrientation(orientation: GroupOrientation): void { @@ -424,8 +447,8 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro } }); - // Update labels - this.updateGroupLabels(); + // Notify group index change given layout has changed + this.notifyGroupIndexChange(); // Restore focus as needed if (restoreFocus) { @@ -484,25 +507,22 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro // Event this._onDidAddGroup.fire(newGroupView); - // Update labels - this.updateGroupLabels(); + // Notify group index change given a new group was added + this.notifyGroupIndexChange(); return newGroupView; } private doCreateGroupView(from?: IEditorGroupView | ISerializedEditorGroup | null): IEditorGroupView { - // Label: just use the number of existing groups as label - const label = this.getGroupLabel(this.count + 1); - // Create group view let groupView: IEditorGroupView; if (from instanceof EditorGroupView) { - groupView = EditorGroupView.createCopy(from, this, label, this.instantiationService); + groupView = EditorGroupView.createCopy(from, this, this.count, this.instantiationService); } else if (isSerializedEditorGroup(from)) { - groupView = EditorGroupView.createFromSerialized(from, this, label, this.instantiationService); + groupView = EditorGroupView.createFromSerialized(from, this, this.count, this.instantiationService); } else { - groupView = EditorGroupView.createNew(this, label, this.instantiationService); + groupView = EditorGroupView.createNew(this, this.count, this.instantiationService); } // Keep in map @@ -516,8 +536,13 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro // Track editor change groupDisposables.add(groupView.onDidGroupChange(e => { - if (e.kind === GroupChangeKind.EDITOR_ACTIVE) { - this.updateContainer(); + switch (e.kind) { + case GroupChangeKind.EDITOR_ACTIVE: + this.updateContainer(); + break; + case GroupChangeKind.GROUP_INDEX: + this._onDidGroupIndexChange.fire(groupView); + break; } })); @@ -600,7 +625,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro } // Remove empty group - if (groupView.isEmpty()) { + if (groupView.isEmpty) { return this.doRemoveEmptyGroup(groupView); } @@ -643,8 +668,8 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro this._activeGroup.focus(); } - // Update labels - this.updateGroupLabels(); + // Notify group index change given a group was removed + this.notifyGroupIndexChange(); // Update container this.updateContainer(); @@ -675,6 +700,9 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro // Event this._onDidMoveGroup.fire(sourceView); + // Notify group index change given a group was moved + this.notifyGroupIndexChange(); + return sourceView; } @@ -715,7 +743,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro }); // Remove source if the view is now empty and not already removed - if (sourceView.isEmpty() && !sourceView.disposed /* could have been disposed already via workbench.editor.closeEmptyGroups setting */) { + if (sourceView.isEmpty && !sourceView.disposed /* could have been disposed already via workbench.editor.closeEmptyGroups setting */) { this.removeGroup(sourceView); } @@ -784,6 +812,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro centerLayout(active: boolean): void { this.centeredLayoutWidget.activate(active); + this._activeGroup.focus(); } @@ -813,6 +842,9 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro // Update container this.updateContainer(); + + // Notify group index change we created the entire grid + this.notifyGroupIndexChange(); } private doCreateGridControlWithPreviousState(): boolean { @@ -906,26 +938,15 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro } private updateContainer(): void { - toggleClass(this.container, 'empty', this.isEmpty()); + toggleClass(this.container, 'empty', this.isEmpty); } - private updateGroupLabels(): void { - - // Since our labels are created using the index of the - // group, adding/removing a group might produce gaps. - // So we iterate over all groups and reassign the label - // based on the index. - this.getGroups(GroupsOrder.GRID_APPEARANCE).forEach((group, index) => { - group.setLabel(this.getGroupLabel(index + 1)); - }); + private notifyGroupIndexChange(): void { + this.getGroups(GroupsOrder.GRID_APPEARANCE).forEach((group, index) => group.notifyIndexChanged(index)); } - private getGroupLabel(index: number): string { - return localize('groupLabel', "Group {0}", index); - } - - private isEmpty(): boolean { - return this.groupViews.size === 1 && this._activeGroup.isEmpty(); + private get isEmpty(): boolean { + return this.groupViews.size === 1 && this._activeGroup.isEmpty; } layout(width: number, height: number): void { @@ -957,7 +978,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro mostRecentActiveGroups: this.mostRecentActiveGroups }; - if (this.isEmpty()) { + if (this.isEmpty) { delete this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY]; } else { this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] = uiState; diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index e60df05192..34978275d4 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -285,4 +285,4 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { if (notificationBorderColor) { collector.addRule(`.monaco-workbench > .notifications-center .notifications-list-container .monaco-list-row[data-last-element="false"] > .notification-list-item { border-bottom: 1px solid ${notificationBorderColor}; }`); } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts index ce199e3165..591648240d 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts @@ -11,12 +11,12 @@ import { localize } from 'vs/nls'; export class NotificationsStatus extends Disposable { - private notificationsCenterStatusItem: IStatusbarEntryAccessor; + private notificationsCenterStatusItem: IStatusbarEntryAccessor | undefined; private currentNotifications = new Set(); private currentStatusMessage: [IStatusMessageViewItem, IDisposable] | undefined; - private isNotificationsCenterVisible: boolean; + private isNotificationsCenterVisible: boolean | undefined; constructor( private model: INotificationsModel, @@ -172,4 +172,4 @@ export class NotificationsStatus extends Disposable { // Remember as current status message this.currentStatusMessage = [item, statusMessageDispose]; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index f212f9cae1..7df648d147 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -62,7 +62,7 @@ export class NotificationsListDelegate implements IListVirtualDelegate { - const action = notification.actions.primary![index]; + const action = primaryActions[index]; button.label = action.label; this.inputDisposables.add(button.onDidClick(e => { diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index 446d677743..d09c121965 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -25,11 +25,19 @@ border-top-style: solid; } +.monaco-workbench.noeditorarea .part.panel.bottom .title { + border-top-width: 0; /* no border when editor area is hiden */ +} + .monaco-workbench .part.panel.right { border-left-width: 1px; border-left-style: solid; } +.monaco-workbench.noeditorarea .part.panel.right { + border-left-width: 0; /* no border when editor area is hiden */ +} + .monaco-workbench .part.panel > .title > .title-actions .monaco-action-bar .action-item .action-label { outline-offset: -2px; } @@ -181,4 +189,4 @@ .hc-black .monaco-workbench .panel.right .minimize-panel-action { background-image: url('chevron-right-hc.svg'); -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 183193bee5..210bcbeba8 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -48,7 +48,7 @@ export class PanelPart extends CompositePart implements IPanelService { private static readonly PINNED_PANELS = 'workbench.panel.pinnedPanels'; private static readonly MIN_COMPOSITE_BAR_WIDTH = 50; - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; //#region IView @@ -83,8 +83,8 @@ export class PanelPart extends CompositePart implements IPanelService { private compositeBar: CompositeBar; private compositeActions: Map = new Map(); - private blockOpeningPanel: boolean; - private _contentDimension: Dimension; + private blockOpeningPanel = false; + private _contentDimension: Dimension | undefined; constructor( @INotificationService notificationService: INotificationService, @@ -367,7 +367,7 @@ export class PanelPart extends CompositePart implements IPanelService { private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { if (e.key === PanelPart.PINNED_PANELS && e.scope === StorageScope.GLOBAL && this.cachedPanelsValue !== this.getStoredCachedPanelsValue() /* This checks if current window changed the value or not */) { - this._cachedPanelsValue = null; + this._cachedPanelsValue = undefined; const newCompositeItems: ICompositeBarItem[] = []; const compositeItems = this.compositeBar.getCompositeBarItems(); const cachedPanels = this.getCachedPanels(); @@ -422,7 +422,7 @@ export class PanelPart extends CompositePart implements IPanelService { return cachedPanels; } - private _cachedPanelsValue: string | null; + private _cachedPanelsValue: string | undefined; private get cachedPanelsValue(): string { if (!this._cachedPanelsValue) { this._cachedPanelsValue = this.getStoredCachedPanelsValue(); diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index a4d6ab0af1..309701dcbe 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -880,7 +880,7 @@ class InputBox extends QuickInput implements IInputBox { export class QuickInputService extends Component implements IQuickInputService { - public _serviceBrand: ServiceIdentifier; + public _serviceBrand!: ServiceIdentifier; private static readonly ID = 'workbench.component.quickinput'; private static readonly MAX_WIDTH = 600; // Max total width of quick open widget diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts index 080c23eac4..1f31fadcde 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts @@ -114,7 +114,7 @@ class ListElementRenderer implements IListRenderer; + _serviceBrand!: ServiceIdentifier; private readonly _onShow: Emitter = this._register(new Emitter()); readonly onShow: Event = this._onShow.event; @@ -861,4 +861,4 @@ export class RemoveFromEditorHistoryAction extends Action { } } -registerSingleton(IQuickOpenService, QuickOpenController, true); \ No newline at end of file +registerSingleton(IQuickOpenService, QuickOpenController, true); diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 0c94ff55f6..f45d78c124 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -31,10 +31,11 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { LayoutPriority } from 'vs/base/browser/ui/grid/grid'; export class SidebarPart extends CompositePart implements IViewletService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; static readonly activeViewletSettingsKey = 'workbench.sidebar.activeviewletid'; @@ -45,6 +46,8 @@ export class SidebarPart extends CompositePart implements IViewletServi readonly minimumHeight: number = 0; readonly maximumHeight: number = Number.POSITIVE_INFINITY; + readonly priority: LayoutPriority = LayoutPriority.Low; + readonly snap = true; get preferredWidth(): number | undefined { @@ -79,7 +82,7 @@ export class SidebarPart extends CompositePart implements IViewletServi private viewletRegistry: ViewletRegistry; private sideBarFocusContextKey: IContextKey; private activeViewletContextKey: IContextKey; - private blockOpeningViewlet: boolean; + private blockOpeningViewlet = false; constructor( @INotificationService notificationService: INotificationService, diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 1a97680666..6b6ed53e9f 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -323,7 +323,7 @@ class HideStatusbarEntryAction extends Action { export class StatusbarPart extends Part implements IStatusbarService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; //#region IView diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index f0bf9b38a9..a055c8c63d 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -58,7 +58,7 @@ export class TitlebarPart extends Part implements ITitleService { private _onMenubarVisibilityChange = this._register(new Emitter()); readonly onMenubarVisibilityChange: Event = this._onMenubarVisibilityChange.event; - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private title: HTMLElement; private dragRegion: HTMLElement; @@ -639,4 +639,4 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { } }); -registerSingleton(ITitleService, TitlebarPart); \ No newline at end of file +registerSingleton(ITitleService, TitlebarPart); diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 6ca9d732a2..a3ea19220b 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/views'; import { Event, Emitter } from 'vs/base/common/event'; -import { IDisposable, Disposable, toDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IAction, IActionViewItem, ActionRunner, Action } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -33,18 +33,14 @@ import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/v import { localize } from 'vs/nls'; import { timeout } from 'vs/base/common/async'; import { editorFindMatchHighlight, editorFindMatchHighlightBorder, textLinkForeground, textCodeBlockBackground, focusBorder } from 'vs/platform/theme/common/colorRegistry'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; import { isString } from 'vs/base/common/types'; -import { renderMarkdown, RenderOptions } from 'vs/base/browser/htmlContentRenderer'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IMarkdownRenderResult } from 'vs/editor/contrib/markdown/markdownRenderer'; import { ILabelService } from 'vs/platform/label/common/label'; import { Registry } from 'vs/platform/registry/common/platform'; import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults'; +import { isFalsyOrWhitespace } from 'vs/base/common/strings'; export class CustomTreeViewPanel extends ViewletPanel { @@ -167,7 +163,7 @@ export class CustomTreeView extends Disposable implements ITreeView { private focused: boolean = false; private domNode: HTMLElement; private treeContainer: HTMLElement; - private _messageValue: string | IMarkdownString | undefined; + private _messageValue: string | undefined; private messageElement: HTMLDivElement; private tree: WorkbenchAsyncDataTree; private treeLabels: ResourceLabels; @@ -175,9 +171,6 @@ export class CustomTreeView extends Disposable implements ITreeView { private elementsToRefresh: ITreeItem[] = []; private menus: TitleMenus; - private markdownRenderer: MarkdownRenderer; - private markdownResult: IMarkdownRenderResult | null; - private readonly _onDidExpandItem: Emitter = this._register(new Emitter()); readonly onDidExpandItem: Event = this._onDidExpandItem.event; @@ -217,12 +210,6 @@ export class CustomTreeView extends Disposable implements ITreeView { this.doRefresh([this.root]); /** soft refresh **/ } })); - this.markdownRenderer = instantiationService.createInstance(MarkdownRenderer); - this._register(toDisposable(() => { - if (this.markdownResult) { - this.markdownResult.dispose(); - } - })); this._register(Registry.as(Extensions.ViewsRegistry).onDidChangeContainer(({ views, from, to }) => { if (from === this.viewContainer && views.some(v => v.id === this.id)) { this.viewContainer = to; @@ -256,12 +243,12 @@ export class CustomTreeView extends Disposable implements ITreeView { } } - private _message: string | IMarkdownString | undefined; - get message(): string | IMarkdownString | undefined { + private _message: string | undefined; + get message(): string | undefined { return this._message; } - set message(message: string | IMarkdownString | undefined) { + set message(message: string | undefined) { this._message = message; this.updateMessage(); } @@ -470,16 +457,13 @@ export class CustomTreeView extends Disposable implements ITreeView { this.updateContentAreas(); } - private showMessage(message: string | IMarkdownString): void { + private showMessage(message: string): void { DOM.removeClass(this.messageElement, 'hide'); if (this._messageValue !== message) { this.resetMessageElement(); this._messageValue = message; - if (isString(this._messageValue)) { + if (!isFalsyOrWhitespace(this._message)) { this.messageElement.textContent = this._messageValue; - } else { - this.markdownResult = this.markdownRenderer.render(this._messageValue); - DOM.append(this.messageElement, this.markdownResult.element); } this.layout(this._height, this._width); } @@ -492,10 +476,6 @@ export class CustomTreeView extends Disposable implements ITreeView { } private resetMessageElement(): void { - if (this.markdownResult) { - this.markdownResult.dispose(); - this.markdownResult = null; - } DOM.clearNode(this.messageElement); } @@ -597,8 +577,16 @@ export class CustomTreeView extends Disposable implements ITreeView { private async doRefresh(elements: ITreeItem[]): Promise { if (this.tree) { this.refreshing = true; - await Promise.all(elements.map(element => this.tree.updateChildren(element, true))); - elements.map(element => this.tree.rerender(element)); + const parents: Set = new Set(); + elements.forEach(element => { + if (element !== this.root) { + const parent = this.tree.getParentElement(element); + parents.add(parent); + } else { + parents.add(element); + } + }); + await Promise.all(Array.from(parents.values()).map(element => this.tree.updateChildren(element, true))); this.refreshing = false; this.updateContentAreas(); if (this.focused) { @@ -718,7 +706,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer { - let uri: URI | undefined; - try { - uri = URI.parse(content); - } catch { - // ignore - } - if (uri && this._openerService) { - this._openerService.open(uri).catch(onUnexpectedError); - } - }, - disposeables - } - }; - } - - render(markdown: IMarkdownString): IMarkdownRenderResult { - const disposeables = new DisposableStore(); - const element: HTMLElement = markdown ? renderMarkdown(markdown, this.getOptions(disposeables)) : document.createElement('span'); - return { - element, - dispose: () => disposeables.dispose() - }; - } -} diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 684b4b01ad..1788c6e4b2 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -626,7 +626,7 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { export class ViewsService extends Disposable implements IViewsService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private readonly viewDescriptorCollections: Map; private readonly viewDisposable: Map; diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index 12b59b918a..a14931d8db 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -76,7 +76,7 @@ export const Extensions = { }; export class ViewletRegistry extends CompositeRegistry { - private defaultViewletId: string; + private defaultViewletId!: string; /** * Registers a viewlet to the platform. diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 5c34d323f1..144d8c51bb 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -40,14 +40,10 @@ import { joinPath } from 'vs/base/common/resources'; import { BrowserStorageService } from 'vs/platform/storage/browser/storageService'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { getThemeTypeSelector, DARK, HIGH_CONTRAST, LIGHT } from 'vs/platform/theme/common/themeService'; -import { IRequestService } from 'vs/platform/request/common/request'; -import { RequestService } from 'vs/workbench/services/request/browser/requestService'; import { InMemoryUserDataProvider } from 'vs/workbench/services/userData/common/inMemoryUserDataProvider'; class CodeRendererMain extends Disposable { - private workbench: Workbench; - constructor( private readonly domElement: HTMLElement, private readonly configuration: IWorkbenchConstructionOptions @@ -65,24 +61,30 @@ class CodeRendererMain extends Disposable { this.restoreBaseTheme(); // Create Workbench - this.workbench = new Workbench( + const workbench = new Workbench( this.domElement, services.serviceCollection, services.logService ); // Layout - this._register(addDisposableListener(window, EventType.RESIZE, () => this.workbench.layout())); + this._register(addDisposableListener(window, EventType.RESIZE, () => workbench.layout())); // Workbench Lifecycle - this._register(this.workbench.onShutdown(() => this.dispose())); - this._register(this.workbench.onWillShutdown(() => { + this._register(workbench.onBeforeShutdown(event => { + if (services.storageService.hasPendingUpdate) { + console.warn('Unload prevented: pending storage update'); + event.veto(true); // prevent data loss from pending storage update + } + })); + this._register(workbench.onWillShutdown(() => { services.storageService.close(); this.saveBaseTheme(); })); + this._register(workbench.onShutdown(() => this.dispose())); // Startup - this.workbench.startup(); + workbench.startup(); } private restoreBaseTheme(): void { @@ -105,7 +107,7 @@ class CodeRendererMain extends Disposable { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // NOTE: DO NOT ADD ANY OTHER SERVICE INTO THE COLLECTION HERE. - // CONTRIBUTE IT VIA WORKBENCH.MAIN.TS AND registerSingleton(). + // CONTRIBUTE IT VIA WORKBENCH.WEB.MAIN.TS AND registerSingleton(). // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // Log @@ -166,7 +168,7 @@ class CodeRendererMain extends Disposable { // User Data Provider fileService.registerProvider(Schemas.userData, userDataProvider); - const [configurationService, storageService] = await Promise.all([ + const services = await Promise.all([ this.createWorkspaceService(payload, environmentService, fileService, remoteAgentService, logService).then(service => { // Workspace @@ -187,10 +189,7 @@ class CodeRendererMain extends Disposable { }) ]); - // Request Service - serviceCollection.set(IRequestService, new RequestService(this.configuration.requestHandler, remoteAgentService, configurationService, logService)); - - return { serviceCollection, logService, storageService }; + return { serviceCollection, logService, storageService: services[1] }; } private async createStorageService(payload: IWorkspaceInitializationPayload, environmentService: IWorkbenchEnvironmentService, fileService: IFileService, logService: ILogService): Promise { diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index d6bbc810ab..a8889821ab 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -22,7 +22,6 @@ import { IRecentlyOpened, IRecent, isRecentFile, isRecentFolder } from 'vs/platf import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; -import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; // tslint:disable-next-line: import-patterns import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { addDisposableListener, EventType } from 'vs/base/browser/dom'; @@ -30,14 +29,17 @@ import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/co import { pathsToEditors } from 'vs/workbench/common/editor'; import { IFileService } from 'vs/platform/files/common/files'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { toStoreData, restoreRecentlyOpened } from 'vs/platform/history/common/historyStorage'; -import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; // tslint:disable-next-line: import-patterns import { IExperimentService, IExperiment, ExperimentActionType, ExperimentState } from 'vs/workbench/contrib/experiments/common/experimentService'; -import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IProductService } from 'vs/platform/product/common/product'; +import Severity from 'vs/base/common/severity'; +import { localize } from 'vs/nls'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; //#region Extension Tips @@ -174,63 +176,6 @@ export class SimpleLogService extends ConsoleLogService { } //#endregion -//#region Multi Extension Management - -export class SimpleMultiExtensionsManagementService implements IExtensionManagementService { - - _serviceBrand: any; - - onInstallExtension = Event.None; - onDidInstallExtension = Event.None; - onUninstallExtension = Event.None; - onDidUninstallExtension = Event.None; - - zip(extension: ILocalExtension): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - - unzip(zipLocation: URI, type: ExtensionType): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - - install(vsix: URI): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - - installFromGallery(extension: IGalleryExtension): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - - uninstall(extension: ILocalExtension, force?: boolean): Promise { - return Promise.resolve(undefined); - } - - reinstallFromGallery(extension: ILocalExtension): Promise { - return Promise.resolve(undefined); - } - - getInstalled(type?: ExtensionType): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - - getExtensionsReport(): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - - updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } -} - -//#endregion - //#region Update export class SimpleUpdateService implements IUpdateService { @@ -571,41 +516,6 @@ registerSingleton(IWindowService, SimpleWindowService); //#endregion -//#region ExtensionHostDebugService - -export class SimpleExtensionHostDebugService extends ExtensionHostDebugChannelClient { - - constructor( - @IRemoteAgentService remoteAgentService: IRemoteAgentService, - //@IWindowService windowService: IWindowService, - @IEnvironmentService environmentService: IEnvironmentService - ) { - const connection = remoteAgentService.getConnection(); - - if (!connection) { - throw new Error('Missing agent connection'); - } - - super(connection.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName)); - - this._register(this.onReload(event => { - if (environmentService.isExtensionDevelopment && environmentService.debugExtensionHost.debugId === event.sessionId) { - //windowService.reloadWindow(); - window.location.reload(); - } - })); - this._register(this.onClose(event => { - if (environmentService.isExtensionDevelopment && environmentService.debugExtensionHost.debugId === event.sessionId) { - //this._windowService.closeWindow(); - window.close(); - } - })); - } -} -registerSingleton(IExtensionHostDebugService, SimpleExtensionHostDebugService); - -//#endregion - //#region Window export class SimpleWindowsService implements IWindowsService { @@ -621,7 +531,10 @@ export class SimpleWindowsService implements IWindowsService { readonly onRecentlyOpenedChange: Event = Event.None; constructor( - @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, + @IDialogService private readonly dialogService: IDialogService, + @IProductService private readonly productService: IProductService, + @IClipboardService private readonly clipboardService: IClipboardService ) { } isFocused(_windowId: number): Promise { @@ -728,6 +641,8 @@ export class SimpleWindowsService implements IWindowsService { } relaunch(_options: { addArgs?: string[], removeArgs?: string[] }): Promise { + window.location.reload(); + return Promise.resolve(); } @@ -753,6 +668,15 @@ export class SimpleWindowsService implements IWindowsService { // we pass the "ParsedArgs" as query parameters of the URL let newAddress = `${document.location.origin}/?`; + let gotFolder = false; + + const addQueryParameter = (key: string, value: string) => { + const lastChar = newAddress.charAt(newAddress.length - 1); + if (lastChar !== '?' && lastChar !== '&') { + newAddress += '&'; + } + newAddress += `${key}=${encodeURIComponent(value)}`; + }; const f = args['folder-uri']; if (f) { @@ -765,9 +689,14 @@ export class SimpleWindowsService implements IWindowsService { u = URI.parse(f); } if (u) { - newAddress += `folder=${encodeURIComponent(u.path)}`; + gotFolder = true; + addQueryParameter('folder', u.path); } } + if (!gotFolder) { + // request empty window + addQueryParameter('ew', 'true'); + } const ep = args['extensionDevelopmentPath']; if (ep) { @@ -780,22 +709,24 @@ export class SimpleWindowsService implements IWindowsService { u = ep; } if (u) { - newAddress += `&edp=${encodeURIComponent(u)}`; + addQueryParameter('edp', u); } } const di = args['debugId']; if (di) { - newAddress += `&di=${encodeURIComponent(di)}`; + addQueryParameter('di', di); } const ibe = args['inspect-brk-extensions']; if (ibe) { - newAddress += `&ibe=${encodeURIComponent(ibe)}`; + addQueryParameter('ibe', ibe); } // add connection token - newAddress += `${this.workbenchEnvironmentService.configuration.connectionToken ? `tkn=${this.workbenchEnvironmentService.configuration.connectionToken}` : ''}`; + if (this.workbenchEnvironmentService.configuration.connectionToken) { + addQueryParameter('tkn', this.workbenchEnvironmentService.configuration.connectionToken); + } window.open(newAddress); @@ -873,8 +804,20 @@ export class SimpleWindowsService implements IWindowsService { throw new Error('not implemented'); } - openAboutDialog(): Promise { - return Promise.resolve(); + async openAboutDialog(): Promise { + const detail = localize('aboutDetail', + "Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}", + this.productService.version || 'Unknown', + this.productService.commit || 'Unknown', + this.productService.date || 'Unknown', + navigator.userAgent + ); + + const result = await this.dialogService.show(Severity.Info, this.productService.nameLong, [localize('copy', "Copy"), localize('ok', "OK")], { detail }); + + if (result === 0) { + this.clipboardService.writeText(detail); + } } resolveProxy(windowId: number, url: string): Promise { diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index e665fae9e8..19c9fba474 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -237,7 +237,7 @@ import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform' 'workbench.useExperimentalGridLayout': { 'type': 'boolean', 'description': nls.localize('workbench.useExperimentalGridLayout', "Enables the grid layout for the workbench. This setting may enable additional layout options for workbench components."), - 'default': false, + 'default': true, 'scope': ConfigurationScope.APPLICATION } } @@ -246,7 +246,7 @@ import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform' // Window let windowTitleDescription = nls.localize('windowTitle', "Controls the window title based on the active editor. Variables are substituted based on the context:"); - windowTitleDescription += [ + windowTitleDescription += '\n- ' + [ nls.localize('activeEditorShort', "`\${activeEditorShort}`: the file name (e.g. myFile.txt)."), nls.localize('activeEditorMedium', "`\${activeEditorMedium}`: the path of the file relative to the workspace folder (e.g. myFolder/myFileFolder/myFile.txt)."), nls.localize('activeEditorLong', "`\${activeEditorLong}`: the full path of the file (e.g. /Users/Development/myFolder/myFileFolder/myFile.txt)."), diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 16dc47a3e2..9f0d903ec0 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -25,7 +25,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { LifecyclePhase, ILifecycleService, WillShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; +import { LifecyclePhase, ILifecycleService, WillShutdownEvent, BeforeShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { NotificationService } from 'vs/workbench/services/notification/common/notificationService'; import { NotificationsCenter } from 'vs/workbench/browser/parts/notifications/notificationsCenter'; @@ -47,6 +47,9 @@ import { Layout } from 'vs/workbench/browser/layout'; export class Workbench extends Layout { + private readonly _onBeforeShutdown = this._register(new Emitter()); + readonly onBeforeShutdown: Event = this._onBeforeShutdown.event; + private readonly _onShutdown = this._register(new Emitter()); readonly onShutdown: Event = this._onShutdown.event; @@ -182,7 +185,7 @@ export class Workbench extends Layout { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // NOTE: DO NOT ADD ANY OTHER SERVICE INTO THE COLLECTION HERE. - // CONTRIBUTE IT VIA WORKBENCH.MAIN.TS AND registerSingleton(). + // CONTRIBUTE IT VIA WORKBENCH.DESKTOP.MAIN.TS AND registerSingleton(). // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // All Contributed Services @@ -225,6 +228,7 @@ export class Workbench extends Layout { ): void { // Lifecycle + this._register(lifecycleService.onBeforeShutdown(event => this._onBeforeShutdown.fire(event))); this._register(lifecycleService.onWillShutdown(event => this._onWillShutdown.fire(event))); this._register(lifecycleService.onShutdown(() => { this._onShutdown.fire(); @@ -238,7 +242,7 @@ export class Workbench extends Layout { this._register(storageService.onWillSaveState(e => this.storeFontInfo(e, storageService))); } - private fontAliasing: 'default' | 'antialiased' | 'none' | 'auto'; + private fontAliasing: 'default' | 'antialiased' | 'none' | 'auto' | undefined; private setFontAliasing(configurationService: IConfigurationService) { const aliasing = configurationService.getValue<'default' | 'antialiased' | 'none' | 'auto'>('workbench.fontAliasing'); if (this.fontAliasing === aliasing) { @@ -294,10 +298,7 @@ export class Workbench extends Layout { 'monaco-workbench', platformClass, isWeb ? 'web' : undefined, - this.state.sideBar.hidden ? 'nosidebar' : undefined, - this.state.panel.hidden ? 'nopanel' : undefined, - this.state.statusBar.hidden ? 'nostatusbar' : undefined, - this.state.fullscreen ? 'fullscreen' : undefined + ...this.getLayoutClasses() ]); addClasses(this.container, ...workbenchClasses); diff --git a/src/vs/workbench/common/contributions.ts b/src/vs/workbench/common/contributions.ts index f5ba05cb55..479757e40d 100644 --- a/src/vs/workbench/common/contributions.ts +++ b/src/vs/workbench/common/contributions.ts @@ -38,8 +38,8 @@ export interface IWorkbenchContributionsRegistry { } class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry { - private instantiationService: IInstantiationService; - private lifecycleService: ILifecycleService; + private instantiationService: IInstantiationService | undefined; + private lifecycleService: ILifecycleService | undefined; private readonly toBeInstantiated: Map[]> = new Map[]>(); @@ -67,7 +67,7 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry this.lifecycleService = accessor.get(ILifecycleService); [LifecyclePhase.Starting, LifecyclePhase.Ready, LifecyclePhase.Restored, LifecyclePhase.Eventually].forEach(phase => { - this.instantiateByPhase(this.instantiationService, this.lifecycleService, phase); + this.instantiateByPhase(this.instantiationService!, this.lifecycleService!, phase); }); } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 190264d869..667cd3fede 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -30,6 +30,8 @@ export const NoEditorsVisibleContext: ContextKeyExpr = EditorsVisibleContext.toN export const TextCompareEditorVisibleContext = new RawContextKey('textCompareEditorVisible', false); export const TextCompareEditorActiveContext = new RawContextKey('textCompareEditorActive', false); export const ActiveEditorGroupEmptyContext = new RawContextKey('activeEditorGroupEmpty', false); +export const ActiveEditorGroupIndexContext = new RawContextKey('activeEditorGroupIndex', -1); +export const ActiveEditorGroupLastContext = new RawContextKey('activeEditorGroupLast', false); export const MultipleEditorGroupsContext = new RawContextKey('multipleEditorGroups', false); export const SingleEditorGroupsContext = MultipleEditorGroupsContext.toNegated(); export const InEditorZenModeContext = new RawContextKey('inZenMode', false); diff --git a/src/vs/workbench/common/editor/textEditorModel.ts b/src/vs/workbench/common/editor/textEditorModel.ts index f3dca48faf..e07dcda857 100644 --- a/src/vs/workbench/common/editor/textEditorModel.ts +++ b/src/vs/workbench/common/editor/textEditorModel.ts @@ -17,8 +17,8 @@ import { withUndefinedAsNull } from 'vs/base/common/types'; * The base text editor model leverages the code editor model. This class is only intended to be subclassed and not instantiated. */ export abstract class BaseTextEditorModel extends EditorModel implements ITextEditorModel, IModeSupport { - protected textEditorModelHandle: URI | null; - private createdEditorModel: boolean; + protected textEditorModelHandle: URI | null = null; + private createdEditorModel: boolean | undefined; private readonly modelDisposeListener = this._register(new MutableDisposable()); diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index 0ba8c77310..a5d74afc5c 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -243,7 +243,7 @@ export interface INotificationViewItem { readonly silent: boolean; readonly message: INotificationMessage; readonly source: string | undefined; - readonly actions: INotificationActions; + readonly actions: INotificationActions | undefined; readonly progress: INotificationViewItemProgress; readonly expanded: boolean; @@ -391,10 +391,10 @@ export class NotificationViewItem extends Disposable implements INotificationVie // RegEx: [, anything not ], ], (, http://|https://|command:, no whitespace) private static LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:)[^\)\s]+)(?: "([^"]+)")?\)/gi; - private _expanded: boolean; + private _expanded: boolean | undefined; - private _actions: INotificationActions; - private _progress: NotificationViewItemProgress; + private _actions: INotificationActions | undefined; + private _progress: NotificationViewItemProgress | undefined; private readonly _onDidExpansionChange: Emitter = this._register(new Emitter()); readonly onDidExpansionChange: Event = this._onDidExpansionChange.event; @@ -505,7 +505,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie } get expanded(): boolean { - return this._expanded; + return !!this._expanded; } get severity(): Severity { @@ -534,6 +534,10 @@ export class NotificationViewItem extends Disposable implements INotificationVie } hasPrompt(): boolean { + if (!this._actions) { + return false; + } + if (!this._actions.primary) { return false; } @@ -562,7 +566,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie return this._source; } - get actions(): INotificationActions { + get actions(): INotificationActions | undefined { return this._actions; } @@ -635,8 +639,8 @@ export class NotificationViewItem extends Disposable implements INotificationVie return false; } - const primaryActions = this._actions.primary || []; - const otherPrimaryActions = other.actions.primary || []; + const primaryActions = (this._actions && this._actions.primary) || []; + const otherPrimaryActions = (other.actions && other.actions.primary) || []; if (primaryActions.length !== otherPrimaryActions.length) { return false; } @@ -704,4 +708,4 @@ class StatusMessageViewItem { return { message, options }; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index afeb6b2e0b..8f45495f44 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -17,7 +17,6 @@ import { values, keys } from 'vs/base/common/map'; import { Registry } from 'vs/platform/registry/common/platform'; import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IAction } from 'vs/base/common/actions'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test'; @@ -305,7 +304,7 @@ export interface ITreeView extends IDisposable { showCollapseAllAction: boolean; - message?: string | IMarkdownString; + message?: string; readonly visible: boolean; diff --git a/src/vs/workbench/contrib/backup/common/backupModelTracker.ts b/src/vs/workbench/contrib/backup/common/backupModelTracker.ts index f3d338b1cf..e2883d5a5e 100644 --- a/src/vs/workbench/contrib/backup/common/backupModelTracker.ts +++ b/src/vs/workbench/contrib/backup/common/backupModelTracker.ts @@ -16,7 +16,7 @@ const AUTO_SAVE_AFTER_DELAY_DISABLED_TIME = CONTENT_CHANGE_EVENT_BUFFER_DELAY + export class BackupModelTracker extends Disposable implements IWorkbenchContribution { - private configuredAutoSaveAfterDelay: boolean; + private configuredAutoSaveAfterDelay = false; constructor( @IBackupFileService private readonly backupFileService: IBackupFileService, @@ -47,11 +47,11 @@ export class BackupModelTracker extends Disposable implements IWorkbenchContribu private onConfigurationChange(configuration: IFilesConfiguration): void { if (!configuration || !configuration.files) { this.configuredAutoSaveAfterDelay = false; + return; } - this.configuredAutoSaveAfterDelay = - (configuration.files.autoSave === AutoSaveConfiguration.AFTER_DELAY && - configuration.files.autoSaveDelay <= AUTO_SAVE_AFTER_DELAY_DISABLED_TIME); + + this.configuredAutoSaveAfterDelay = (configuration.files.autoSave === AutoSaveConfiguration.AFTER_DELAY && configuration.files.autoSaveDelay <= AUTO_SAVE_AFTER_DELAY_DISABLED_TIME); } private onTextFileModelChanged(event: TextFileModelChangeEvent): void { @@ -81,4 +81,4 @@ export class BackupModelTracker extends Disposable implements IWorkbenchContribu private discardBackup(resource: Uri): void { this.backupFileService.discardResourceBackup(resource); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index 8f17e0b4b3..5d068f18f3 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -91,15 +91,15 @@ class LayoutInfo { export class CallHierarchyTreePeekWidget extends PeekViewWidget { - private _changeDirectionAction: ChangeHierarchyDirectionAction; - private _parent: HTMLElement; - private _message: HTMLElement; - private _splitView: SplitView; - private _tree: WorkbenchAsyncDataTree; + private _changeDirectionAction?: ChangeHierarchyDirectionAction; + private _parent!: HTMLElement; + private _message!: HTMLElement; + private _splitView!: SplitView; + private _tree!: WorkbenchAsyncDataTree; private _treeViewStates = new Map(); - private _editor: EmbeddedCodeEditorWidget; - private _dim: Dimension; - private _layoutInfo: LayoutInfo; + private _editor!: EmbeddedCodeEditorWidget; + private _dim!: Dimension; + private _layoutInfo!: LayoutInfo; constructor( editor: ICodeEditor, @@ -386,7 +386,7 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { }; this._changeDirectionAction = new ChangeHierarchyDirectionAction(this._direction, changeDirection); this._disposables.add(this._changeDirectionAction); - this._actionbarWidget.push(this._changeDirectionAction, { icon: true, label: false }); + this._actionbarWidget!.push(this._changeDirectionAction, { icon: true, label: false }); } } diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts index 820419741f..97cdd1eb4b 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts @@ -58,7 +58,9 @@ export class IdentityProvider implements IIdentityProvider { } class CallRenderingTemplate { - readonly iconLabel: IconLabel; + constructor( + readonly iconLabel: IconLabel + ) { } } export class CallRenderer implements ITreeRenderer { @@ -69,7 +71,7 @@ export class CallRenderer implements ITreeRenderer, _index: number, template: CallRenderingTemplate): void { const { element, filterData } = node; diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-next-dark.svg b/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-next-dark.svg new file mode 100644 index 0000000000..dbe70d742d --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-next-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-next-light.svg b/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-next-light.svg new file mode 100644 index 0000000000..ec824f41cc --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-next-light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-previous-dark.svg b/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-previous-dark.svg new file mode 100644 index 0000000000..5db4f79da8 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-previous-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-previous-light.svg b/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-previous-light.svg new file mode 100644 index 0000000000..aac3a5020c --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-previous-light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/images/close-dark.svg b/src/vs/workbench/contrib/codeEditor/browser/find/images/close-dark.svg new file mode 100644 index 0000000000..75644595d1 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/find/images/close-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/images/close-light.svg b/src/vs/workbench/contrib/codeEditor/browser/find/images/close-light.svg new file mode 100644 index 0000000000..cf5f28ca35 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/find/images/close-light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/editor/contrib/find/simpleFindWidget.css b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css similarity index 100% rename from src/vs/editor/contrib/find/simpleFindWidget.css rename to src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css diff --git a/src/vs/editor/contrib/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts similarity index 99% rename from src/vs/editor/contrib/find/simpleFindWidget.ts rename to src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts index 8277a12054..3fa388fd0f 100644 --- a/src/vs/editor/contrib/find/simpleFindWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts @@ -90,6 +90,7 @@ export abstract class SimpleFindWidget extends Widget { this._findInput.setRegex(this._state.isRegex); this._findInput.setWholeWords(this._state.wholeWord); this._findInput.setCaseSensitive(this._state.matchCase); + this.findFirst(); })); this._register(this._findInput.onKeyDown((e) => { @@ -165,6 +166,7 @@ export abstract class SimpleFindWidget extends Widget { protected abstract onInputChanged(): boolean; protected abstract find(previous: boolean): void; + protected abstract findFirst(): void; protected abstract onFocusTrackerFocus(): void; protected abstract onFocusTrackerBlur(): void; protected abstract onFindInputFocusTrackerFocus(): void; diff --git a/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts b/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts index de41caecca..c6965c586c 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts @@ -11,7 +11,6 @@ import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; /** * Shows a message when opening a large file which has been memory optimized (and features disabled). @@ -20,26 +19,19 @@ export class LargeFileOptimizationsWarner extends Disposable implements IEditorC private static readonly ID = 'editor.contrib.largeFileOptimizationsWarner'; - private _isDisabled: boolean; - constructor( private readonly _editor: ICodeEditor, @INotificationService private readonly _notificationService: INotificationService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @IStorageService private readonly _storageService: IStorageService, ) { super(); - this._isDisabled = Boolean(this._storageService.getBoolean('editor.neverPromptForLargeFiles', StorageScope.GLOBAL, false)); this._register(this._editor.onDidChangeModel((e) => { const model = this._editor.getModel(); if (!model) { return; } - if (this._isDisabled) { - return; - } if (model.isTooLargeForTokenization()) { const message = nls.localize( @@ -54,13 +46,6 @@ export class LargeFileOptimizationsWarner extends Disposable implements IEditorC ); this._notificationService.prompt(Severity.Info, message, [ - { - label: nls.localize('dontShowAgain', "Don't Show Again"), - run: () => { - this._isDisabled = true; - this._storageService.store('editor.neverPromptForLargeFiles', true, StorageScope.GLOBAL); - } - }, { label: nls.localize('removeOptimizations', "Forcefully enable features"), run: () => { @@ -71,7 +56,7 @@ export class LargeFileOptimizationsWarner extends Disposable implements IEditorC }); } } - ]); + ], { neverShowAgain: { id: 'editor.contrib.largeFileOptimizationsWarner' } }); } })); } diff --git a/src/vs/workbench/contrib/comments/browser/commentFormActions.ts b/src/vs/workbench/contrib/comments/browser/commentFormActions.ts index ef737c498a..c661279638 100644 --- a/src/vs/workbench/contrib/comments/browser/commentFormActions.ts +++ b/src/vs/workbench/contrib/comments/browser/commentFormActions.ts @@ -14,7 +14,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; export class CommentFormActions implements IDisposable { private _buttonElements: HTMLElement[] = []; private readonly _toDispose = new DisposableStore(); - private _actions: IAction[]; + private _actions: IAction[] = []; constructor( private container: HTMLElement, @@ -59,4 +59,4 @@ export class CommentFormActions implements IDisposable { dispose() { this._toDispose.dispose(); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts b/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts index 2c8b0dc957..80625ed5d9 100644 --- a/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts @@ -16,7 +16,7 @@ const overviewRulerDefault = new Color(new RGBA(197, 197, 197, 1)); export const overviewRulerCommentingRangeForeground = registerColor('editorGutter.commentRangeForeground', { dark: overviewRulerDefault, light: overviewRulerDefault, hc: overviewRulerDefault }, nls.localize('editorGutterCommentRangeForeground', 'Editor gutter decoration color for commenting ranges.')); export class CommentGlyphWidget { - private _lineNumber: number; + private _lineNumber!: number; private _editor: ICodeEditor; private commentsDecorations: string[] = []; private _commentsOptions: ModelDecorationOptions; diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 90931e52de..a00d3812ba 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -40,22 +40,22 @@ export class CommentNode extends Disposable { private _md: HTMLElement; private _clearTimeout: any; - private _editAction: Action; - private _commentEditContainer: HTMLElement; + private _editAction: Action | null = null; + private _commentEditContainer: HTMLElement | null = null; private _commentDetailsContainer: HTMLElement; - private _actionsToolbarContainer: HTMLElement; + private _actionsToolbarContainer!: HTMLElement; private _reactionsActionBar?: ActionBar; private _reactionActionsContainer?: HTMLElement; - private _commentEditor: SimpleCommentEditor | null; + private _commentEditor: SimpleCommentEditor | null = null; private _commentEditorDisposables: IDisposable[] = []; - private _commentEditorModel: ITextModel; - private _isPendingLabel: HTMLElement; + private _commentEditorModel: ITextModel | null = null; + private _isPendingLabel!: HTMLElement; private _contextKeyService: IContextKeyService; private _commentContextValue: IContextKey; protected actionRunner?: IActionRunner; protected toolbar: ToolBar | undefined; - private _commentFormActions: CommentFormActions; + private _commentFormActions: CommentFormActions | null = null; private _onDidDelete = new Emitter(); @@ -63,7 +63,7 @@ export class CommentNode extends Disposable { return this._domNode; } - public isEditing: boolean; + public isEditing: boolean = false; constructor( private commentThread: modes.CommentThread, @@ -326,8 +326,8 @@ export class CommentNode extends Disposable { } } - private createCommentEditor(): void { - const container = dom.append(this._commentEditContainer, dom.$('.edit-textarea')); + private createCommentEditor(editContainer: HTMLElement): void { + const container = dom.append(editContainer, dom.$('.edit-textarea')); this._commentEditor = this.instantiationService.createInstance(SimpleCommentEditor, container, SimpleCommentEditor.getEditorOptions(), this.parentEditor, this.parentThread); const resource = URI.parse(`comment:commentinput-${this.comment.uniqueIdInThread}-${Date.now()}.md`); this._commentEditorModel = this.modelService.createModel('', this.modeService.createByFilepathOrFirstLine(resource), resource, false); @@ -390,7 +390,7 @@ export class CommentNode extends Disposable { this._commentEditor = null; } - this._commentEditContainer.remove(); + this._commentEditContainer!.remove(); } public switchToEditMode() { @@ -401,7 +401,7 @@ export class CommentNode extends Disposable { this.isEditing = true; this._body.classList.add('hidden'); this._commentEditContainer = dom.append(this._commentDetailsContainer, dom.$('.edit-container')); - this.createCommentEditor(); + this.createCommentEditor(this._commentEditContainer); const formActions = dom.append(this._commentEditContainer, dom.$('.form-actions')); const menus = this.commentService.getCommentMenus(this.owner); @@ -409,7 +409,9 @@ export class CommentNode extends Disposable { this._register(menu); this._register(menu.onDidChange(() => { - this._commentFormActions.setActions(menu); + if (this._commentFormActions) { + this._commentFormActions.setActions(menu); + } })); this._commentFormActions = new CommentFormActions(formActions, (action: IAction): void => { diff --git a/src/vs/workbench/contrib/comments/browser/commentsPanel.ts b/src/vs/workbench/contrib/comments/browser/commentsPanel.ts index a0de87828b..f23396f2a3 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsPanel.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsPanel.ts @@ -4,34 +4,31 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/panel'; +import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; -import { IAction } from 'vs/base/common/actions'; -import { Event } from 'vs/base/common/event'; -import { CollapseAllAction, DefaultAccessibilityProvider, DefaultController, DefaultDragAndDrop } from 'vs/base/parts/tree/browser/treeDefaults'; +import { IAction, Action } from 'vs/base/common/actions'; +import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TreeResourceNavigator, WorkbenchTree } from 'vs/platform/list/browser/listService'; +import { TreeResourceNavigator2 } from 'vs/platform/list/browser/listService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Panel } from 'vs/workbench/browser/panel'; import { CommentNode, CommentsModel, ResourceWithCommentThreads, ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel'; import { ReviewController } from 'vs/workbench/contrib/comments/browser/commentsEditorContribution'; -import { CommentsDataFilter, CommentsDataSource, CommentsModelRenderer } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; import { ICommentService, IWorkspaceCommentThreadsEvent } from 'vs/workbench/contrib/comments/browser/commentService'; import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { textLinkForeground, textLinkActiveForeground, focusBorder, textPreformatForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ResourceLabels } from 'vs/workbench/browser/labels'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { CommentsList, COMMENTS_PANEL_ID, COMMENTS_PANEL_TITLE } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; -export const COMMENTS_PANEL_ID = 'workbench.panel.comments'; -export const COMMENTS_PANEL_TITLE = 'Comments'; export class CommentsPanel extends Panel { private treeLabels: ResourceLabels; - private tree: WorkbenchTree; + private tree: CommentsList; private treeContainer: HTMLElement; private messageBoxContainer: HTMLElement; private messageBox: HTMLElement; @@ -42,7 +39,6 @@ export class CommentsPanel extends Panel { @IInstantiationService private readonly instantiationService: IInstantiationService, @ICommentService private readonly commentService: ICommentService, @IEditorService private readonly editorService: IEditorService, - @IOpenerService private readonly openerService: IOpenerService, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IStorageService storageService: IStorageService @@ -113,7 +109,7 @@ export class CommentsPanel extends Panel { public getActions(): IAction[] { if (!this.collapseAllAction) { - this.collapseAllAction = this.instantiationService.createInstance(CollapseAllAction, this.tree, this.commentsModel.hasCommentThreads()); + this.collapseAllAction = new Action('vs.tree.collapse', nls.localize('collapseAll', "Collapse All"), 'monaco-tree-action collapse-all', true, () => this.tree ? new CollapseAllAction(this.tree, true).run() : Promise.resolve()); this._register(this.collapseAllAction); } @@ -141,22 +137,11 @@ export class CommentsPanel extends Panel { private createTree(): void { this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this)); + this.tree = this._register(this.instantiationService.createInstance(CommentsList, this.treeLabels, this.treeContainer)); - this.tree = this._register(this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, { - dataSource: new CommentsDataSource(), - renderer: new CommentsModelRenderer(this.treeLabels, this.openerService), - accessibilityProvider: new DefaultAccessibilityProvider, - controller: new DefaultController(), - dnd: new DefaultDragAndDrop(), - filter: new CommentsDataFilter() - }, { - twistiePixels: 20, - ariaLabel: COMMENTS_PANEL_TITLE - })); - - const commentsNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: true })); - this._register(Event.debounce(commentsNavigator.openResource, (last, event) => event, 100, true)(options => { - this.openFile(options.element, options.editorOptions.pinned, options.editorOptions.preserveFocus, options.sideBySide); + const commentsNavigator = this._register(new TreeResourceNavigator2(this.tree, { openOnFocus: true })); + this._register(commentsNavigator.onDidOpenResource(e => { + this.openFile(e.element, e.editorOptions.pinned, e.editorOptions.preserveFocus, e.sideBySide); })); } @@ -213,7 +198,7 @@ export class CommentsPanel extends Panel { this.collapseAllAction.enabled = this.commentsModel.hasCommentThreads(); dom.toggleClass(this.treeContainer, 'hidden', !this.commentsModel.hasCommentThreads()); - this.tree.refresh().then(() => { + this.tree.updateChildren().then(() => { this.renderMessage(); }, (e) => { console.log(e); @@ -243,4 +228,4 @@ CommandsRegistry.registerCommand({ panelService.openPanel(COMMENTS_PANEL_ID, true); } } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 19462874ec..99c38a87c6 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -9,30 +9,28 @@ import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { IDataSource, IFilter, IRenderer as ITreeRenderer, ITree } from 'vs/base/parts/tree/browser/tree'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; import { CommentNode, CommentsModel, ResourceWithCommentThreads } from 'vs/workbench/contrib/comments/common/commentModel'; +import { IAsyncDataSource, ITreeNode } from 'vs/base/browser/ui/tree/tree'; +import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { WorkbenchAsyncDataTree, IListService } from 'vs/platform/list/browser/listService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -export class CommentsDataSource implements IDataSource { - public getId(tree: ITree, element: any): string { - if (element instanceof CommentsModel) { - return 'root'; - } - if (element instanceof ResourceWithCommentThreads) { - return `${element.owner}-${element.id}`; - } - if (element instanceof CommentNode) { - return `${element.owner}-${element.resource.toString()}-${element.threadId}-${element.comment.uniqueIdInThread}` + (element.isRoot ? '-root' : ''); - } - return ''; - } +export const COMMENTS_PANEL_ID = 'workbench.panel.comments'; +export const COMMENTS_PANEL_TITLE = 'Comments'; - public hasChildren(tree: ITree, element: any): boolean { +export class CommentsAsyncDataSource implements IAsyncDataSource { + hasChildren(element: any): boolean { return element instanceof CommentsModel || element instanceof ResourceWithCommentThreads || (element instanceof CommentNode && !!element.replies.length); } - public getChildren(tree: ITree, element: any): Promise { + getChildren(element: any): any[] | Promise { if (element instanceof CommentsModel) { return Promise.resolve(element.resourceCommentThreads); } @@ -44,14 +42,6 @@ export class CommentsDataSource implements IDataSource { } return Promise.resolve([]); } - - public getParent(tree: ITree, element: any): Promise { - return Promise.resolve(undefined); - } - - public shouldAutoexpand(tree: ITree, element: any): boolean { - return true; - } } interface IResourceTemplateData { @@ -65,61 +55,36 @@ interface ICommentThreadTemplateData { disposables: IDisposable[]; } -export class CommentsModelRenderer implements ITreeRenderer { +export class CommentsModelVirualDelegate implements IListVirtualDelegate { private static RESOURCE_ID = 'resource-with-comments'; private static COMMENT_ID = 'comment-node'; - constructor( - private labels: ResourceLabels, - @IOpenerService private readonly openerService: IOpenerService - ) { - } - public getHeight(tree: ITree, element: any): number { + getHeight(element: any): number { return 22; } - public getTemplateId(tree: ITree, element: any): string { + public getTemplateId(element: any): string { if (element instanceof ResourceWithCommentThreads) { - return CommentsModelRenderer.RESOURCE_ID; + return CommentsModelVirualDelegate.RESOURCE_ID; } if (element instanceof CommentNode) { - return CommentsModelRenderer.COMMENT_ID; + return CommentsModelVirualDelegate.COMMENT_ID; } return ''; } +} - public renderTemplate(ITree: ITree, templateId: string, container: HTMLElement): any { - switch (templateId) { - case CommentsModelRenderer.RESOURCE_ID: - return this.renderResourceTemplate(container); - case CommentsModelRenderer.COMMENT_ID: - return this.renderCommentTemplate(container); - } +export class ResourceWithCommentsRenderer implements IListRenderer, IResourceTemplateData> { + templateId: string = 'resource-with-comments'; + + constructor( + private labels: ResourceLabels + ) { } - public disposeTemplate(tree: ITree, templateId: string, templateData: any): void { - switch (templateId) { - case CommentsModelRenderer.RESOURCE_ID: - (templateData).resourceLabel.dispose(); - break; - case CommentsModelRenderer.COMMENT_ID: - (templateData).disposables.forEach(disposeable => disposeable.dispose()); - break; - } - } - - public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void { - switch (templateId) { - case CommentsModelRenderer.RESOURCE_ID: - return this.renderResourceElement(tree, element, templateData); - case CommentsModelRenderer.COMMENT_ID: - return this.renderCommentElement(tree, element, templateData); - } - } - - private renderResourceTemplate(container: HTMLElement): IResourceTemplateData { + renderTemplate(container: HTMLElement) { const data = Object.create(null); const labelContainer = dom.append(container, dom.$('.resource-container')); data.resourceLabel = this.labels.create(labelContainer); @@ -127,7 +92,23 @@ export class CommentsModelRenderer implements ITreeRenderer { return data; } - private renderCommentTemplate(container: HTMLElement): ICommentThreadTemplateData { + renderElement(node: ITreeNode, index: number, templateData: IResourceTemplateData, height: number | undefined): void { + templateData.resourceLabel.setFile(node.element.resource); + } + + disposeTemplate(templateData: IResourceTemplateData): void { + templateData.resourceLabel.dispose(); + } +} + +export class CommentNodeRenderer implements IListRenderer, ICommentThreadTemplateData> { + templateId: string = 'comment-node'; + + constructor( + @IOpenerService private readonly openerService: IOpenerService + ) { } + + renderTemplate(container: HTMLElement) { const data = Object.create(null); const labelContainer = dom.append(container, dom.$('.comment-container')); data.userName = dom.append(labelContainer, dom.$('.user')); @@ -137,16 +118,12 @@ export class CommentsModelRenderer implements ITreeRenderer { return data; } - private renderResourceElement(tree: ITree, element: ResourceWithCommentThreads, templateData: IResourceTemplateData) { - templateData.resourceLabel.setFile(element.resource); - } - - private renderCommentElement(tree: ITree, element: CommentNode, templateData: ICommentThreadTemplateData) { - templateData.userName.textContent = element.comment.userName; + renderElement(node: ITreeNode, index: number, templateData: ICommentThreadTemplateData, height: number | undefined): void { + templateData.userName.textContent = node.element.comment.userName; templateData.commentText.innerHTML = ''; const disposables = new DisposableStore(); templateData.disposables.push(disposables); - const renderedComment = renderMarkdown(element.comment.body, { + const renderedComment = renderMarkdown(node.element.comment.body, { inline: true, actionHandler: { callback: (content) => { @@ -171,16 +148,71 @@ export class CommentsModelRenderer implements ITreeRenderer { templateData.commentText.appendChild(renderedComment); } -} -export class CommentsDataFilter implements IFilter { - public isVisible(tree: ITree, element: any): boolean { - if (element instanceof CommentsModel) { - return element.resourceCommentThreads.length > 0; - } - if (element instanceof ResourceWithCommentThreads) { - return element.commentThreads.length > 0; - } - return true; + disposeTemplate(templateData: ICommentThreadTemplateData): void { + templateData.disposables.forEach(disposeable => disposeable.dispose()); + } +} + +export class CommentsList extends WorkbenchAsyncDataTree { + constructor( + labels: ResourceLabels, + container: HTMLElement, + @IContextKeyService contextKeyService: IContextKeyService, + @IListService listService: IListService, + @IThemeService themeService: IThemeService, + @IInstantiationService instantiationService: IInstantiationService, + @IConfigurationService configurationService: IConfigurationService, + @IKeybindingService keybindingService: IKeybindingService, + @IAccessibilityService accessibilityService: IAccessibilityService + ) { + const delegate = new CommentsModelVirualDelegate(); + const dataSource = new CommentsAsyncDataSource(); + + const renderers = [ + instantiationService.createInstance(ResourceWithCommentsRenderer, labels), + instantiationService.createInstance(CommentNodeRenderer) + ]; + + super( + container, + delegate, + renderers, + dataSource, + { + ariaLabel: COMMENTS_PANEL_TITLE, + keyboardSupport: true, + identityProvider: { + getId: (element: any) => { + if (element instanceof CommentsModel) { + return 'root'; + } + if (element instanceof ResourceWithCommentThreads) { + return `${element.owner}-${element.id}`; + } + if (element instanceof CommentNode) { + return `${element.owner}-${element.resource.toString()}-${element.threadId}-${element.comment.uniqueIdInThread}` + (element.isRoot ? '-root' : ''); + } + return ''; + } + }, + expandOnlyOnTwistieClick: (element: any) => { + if (element instanceof CommentsModel || element instanceof ResourceWithCommentThreads) { + return false; + } + + return true; + }, + collapseByDefault: () => { + return false; + } + }, + contextKeyService, + listService, + themeService, + configurationService, + keybindingService, + accessibilityService + ); } } diff --git a/src/vs/workbench/contrib/comments/browser/reactionsAction.ts b/src/vs/workbench/contrib/comments/browser/reactionsAction.ts index 67876e7ada..9660601b9e 100644 --- a/src/vs/workbench/contrib/comments/browser/reactionsAction.ts +++ b/src/vs/workbench/contrib/comments/browser/reactionsAction.ts @@ -11,11 +11,10 @@ import { URI, UriComponents } from 'vs/base/common/uri'; export class ToggleReactionsAction extends Action { static readonly ID = 'toolbar.toggle.pickReactions'; - private _menuActions: IAction[]; + private _menuActions: IAction[] = []; private toggleDropdownMenu: () => void; constructor(toggleDropdownMenu: () => void, title?: string) { - title = title || nls.localize('pickReactions', "Pick Reactions..."); - super(ToggleReactionsAction.ID, title, 'toggle-reactions', true); + super(ToggleReactionsAction.ID, title || nls.localize('pickReactions', "Pick Reactions..."), 'toggle-reactions', true); this.toggleDropdownMenu = toggleDropdownMenu; } run(): Promise { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index a4d5c47d7c..3d8701c5ca 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -45,10 +45,10 @@ export interface IPrivateBreakpointWidgetService { const DECORATION_KEY = 'breakpointwidgetdecoration'; export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWidgetService { - public _serviceBrand: any; + _serviceBrand: any; - private selectContainer: HTMLElement; - private input: IActiveCodeEditor; + private selectContainer!: HTMLElement; + private input!: IActiveCodeEditor; private toDispose: lifecycle.IDisposable[]; private conditionInput = ''; private hitCountInput = ''; @@ -130,12 +130,12 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi } } - public show(rangeOrPos: IRange | IPosition, heightInLines: number) { + show(rangeOrPos: IRange | IPosition, heightInLines: number) { const lineNum = this.input.getModel().getLineCount(); super.show(rangeOrPos, lineNum + 1); } - public fitHeightToContent() { + fitHeightToContent() { const lineNum = this.input.getModel().getLineCount(); this._relayout(lineNum + 1); } @@ -165,50 +165,6 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi setTimeout(() => this.input.focus(), 150); } - public close(success: boolean): void { - if (success) { - // if there is already a breakpoint on this location - remove it. - - let condition = this.breakpoint && this.breakpoint.condition; - let hitCondition = this.breakpoint && this.breakpoint.hitCondition; - let logMessage = this.breakpoint && this.breakpoint.logMessage; - this.rememberInput(); - - if (this.conditionInput || this.context === Context.CONDITION) { - condition = this.conditionInput; - } - if (this.hitCountInput || this.context === Context.HIT_COUNT) { - hitCondition = this.hitCountInput; - } - if (this.logMessageInput || this.context === Context.LOG_MESSAGE) { - logMessage = this.logMessageInput; - } - - if (this.breakpoint) { - const data = new Map(); - data.set(this.breakpoint.getId(), { - condition, - hitCondition, - logMessage - }); - this.debugService.updateBreakpoints(this.breakpoint.uri, data, false).then(undefined, onUnexpectedError); - } else { - const model = this.editor.getModel(); - if (model) { - this.debugService.addBreakpoints(model.uri, [{ - lineNumber: this.lineNumber, - enabled: true, - condition, - hitCondition, - logMessage - }], `breakpointWidget`); - } - } - } - - this.dispose(); - } - protected _doLayout(heightInPixel: number, widthInPixel: number): void { this.input.layout({ height: heightInPixel, width: widthInPixel - 113 }); } @@ -305,7 +261,51 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi return false; } - public dispose(): void { + close(success: boolean): void { + if (success) { + // if there is already a breakpoint on this location - remove it. + + let condition = this.breakpoint && this.breakpoint.condition; + let hitCondition = this.breakpoint && this.breakpoint.hitCondition; + let logMessage = this.breakpoint && this.breakpoint.logMessage; + this.rememberInput(); + + if (this.conditionInput || this.context === Context.CONDITION) { + condition = this.conditionInput; + } + if (this.hitCountInput || this.context === Context.HIT_COUNT) { + hitCondition = this.hitCountInput; + } + if (this.logMessageInput || this.context === Context.LOG_MESSAGE) { + logMessage = this.logMessageInput; + } + + if (this.breakpoint) { + const data = new Map(); + data.set(this.breakpoint.getId(), { + condition, + hitCondition, + logMessage + }); + this.debugService.updateBreakpoints(this.breakpoint.uri, data, false).then(undefined, onUnexpectedError); + } else { + const model = this.editor.getModel(); + if (model) { + this.debugService.addBreakpoints(model.uri, [{ + lineNumber: this.lineNumber, + enabled: true, + condition, + hitCondition, + logMessage + }], `breakpointWidget`); + } + } + } + + this.dispose(); + } + + dispose(): void { super.dispose(); this.input.dispose(); lifecycle.dispose(this.toDispose); @@ -327,7 +327,7 @@ class AcceptBreakpointWidgetInputAction extends EditorCommand { }); } - public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor): void { + runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor): void { accessor.get(IPrivateBreakpointWidgetService).close(true); } } @@ -347,7 +347,7 @@ class CloseBreakpointWidgetCommand extends EditorCommand { }); } - public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { const debugContribution = editor.getContribution(EDITOR_CONTRIBUTION_ID); if (debugContribution) { // if focus is in outer editor we need to use the debug contribution to close diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 3ccaa40538..f6ec5df703 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -45,8 +45,8 @@ function createCheckbox(): HTMLInputElement { export class BreakpointsView extends ViewletPanel { private static readonly MAX_VISIBLE_FILES = 9; - private list: WorkbenchList; - private needsRefresh: boolean; + private list!: WorkbenchList; + private needsRefresh = false; constructor( options: IViewletViewOptions, diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 8be06f2b2e..b1d16682e1 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -37,15 +37,15 @@ type CallStackItem = IStackFrame | IThread | IDebugSession | string | ThreadAndS export class CallStackView extends ViewletPanel { - private pauseMessage: HTMLSpanElement; - private pauseMessageLabel: HTMLSpanElement; + private pauseMessage!: HTMLSpanElement; + private pauseMessageLabel!: HTMLSpanElement; private onCallStackChangeScheduler: RunOnceScheduler; - private needsRefresh: boolean; - private ignoreSelectionChangedEvent: boolean; - private ignoreFocusStackFrameEvent: boolean; + private needsRefresh = false; + private ignoreSelectionChangedEvent = false; + private ignoreFocusStackFrameEvent = false; private callStackItemType: IContextKey; - private dataSource: CallStackDataSource; - private tree: WorkbenchAsyncDataTree; + private dataSource!: CallStackDataSource; + private tree!: WorkbenchAsyncDataTree; private contributedContextMenu: IMenu; private parentSessionToExpand = new Set(); @@ -584,7 +584,7 @@ function isDeemphasized(frame: IStackFrame): boolean { } class CallStackDataSource implements IAsyncDataSource { - deemphasizedStackFramesToShow: IStackFrame[]; + deemphasizedStackFramesToShow: IStackFrame[] = []; constructor(private debugService: IDebugService) { } diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index b372a59234..dbd946331c 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -260,6 +260,11 @@ configurationRegistry.registerConfiguration({ description: nls.localize({ comment: ['This is the description for a setting'], key: 'launch' }, "Global debug launch configuration. Should be used as an alternative to 'launch.json' that is shared across workspaces."), default: { configurations: [], compounds: [] }, $ref: launchSchemaId + }, + 'debug.focusWindowOnBreak': { + type: 'boolean', + description: nls.localize('debug.focusWindowOnBreak', "Controls whether the workbench window should be focused when the debugger breaks."), + default: true } } }); diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index 4ac8043eeb..9290abdbe2 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -27,13 +27,13 @@ export class StartDebugActionViewItem implements IActionViewItem { private static readonly SEPARATOR = '─────────'; - public actionRunner: IActionRunner; - private container: HTMLElement; - private start: HTMLElement; + actionRunner!: IActionRunner; + private container!: HTMLElement; + private start!: HTMLElement; private selectBox: SelectBox; - private options: { label: string, handler?: (() => boolean) }[]; + private options: { label: string, handler?: (() => boolean) }[] = []; private toDispose: IDisposable[]; - private selected: number; + private selected = 0; constructor( private context: any, @@ -66,7 +66,7 @@ export class StartDebugActionViewItem implements IActionViewItem { })); } - public render(container: HTMLElement): void { + render(container: HTMLElement): void { this.container = container; dom.addClass(container, 'start-debug-action-item'); this.start = dom.append(container, $('.icon')); @@ -129,15 +129,15 @@ export class StartDebugActionViewItem implements IActionViewItem { this.updateOptions(); } - public setActionContext(context: any): void { + setActionContext(context: any): void { this.context = context; } - public isEnabled(): boolean { + isEnabled(): boolean { return true; } - public focus(fromRight?: boolean): void { + focus(fromRight?: boolean): void { if (fromRight) { this.selectBox.focus(); } else { @@ -145,11 +145,11 @@ export class StartDebugActionViewItem implements IActionViewItem { } } - public blur(): void { + blur(): void { this.container.blur(); } - public dispose(): void { + dispose(): void { this.toDispose = dispose(this.toDispose); } diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 415c6a2a97..cf0997c6e6 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -34,6 +34,7 @@ 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'; const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); jsonRegistry.registerSchema(launchSchemaId, launchSchema); @@ -44,7 +45,7 @@ const DEBUG_SELECTED_ROOT = 'debug.selectedroot'; export class ConfigurationManager implements IConfigurationManager { private debuggers: Debugger[]; private breakpointModeIdsSet = new Set(); - private launches: ILaunch[]; + private launches!: ILaunch[]; private selectedName: string | undefined; private selectedLaunch: ILaunch | undefined; private toDispose: IDisposable[]; @@ -180,7 +181,7 @@ export class ConfigurationManager implements IConfigurationManager { return providers.length > 0; } - resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: IConfig): Promise { + resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: IConfig, token: CancellationToken): Promise { return this.activateDebuggers('onDebugResolve', type).then(() => { // pipe the config through the promises sequentially. Append at the end the '*' types const providers = this.configProviders.filter(p => p.type === type && p.resolveDebugConfiguration) @@ -189,7 +190,7 @@ export class ConfigurationManager implements IConfigurationManager { return providers.reduce((promise, provider) => { return promise.then(config => { if (config) { - return provider.resolveDebugConfiguration!(folderUri, config); + return provider.resolveDebugConfiguration!(folderUri, config, token); } else { return Promise.resolve(config); } @@ -198,9 +199,9 @@ export class ConfigurationManager implements IConfigurationManager { }); } - provideDebugConfigurations(folderUri: uri | undefined, type: string): Promise { + provideDebugConfigurations(folderUri: uri | undefined, type: string, token: CancellationToken): Promise { return this.activateDebuggers('onDebugInitialConfigurations') - .then(() => Promise.all(this.configProviders.filter(p => p.type === type && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations!(folderUri))) + .then(() => Promise.all(this.configProviders.filter(p => p.type === type && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations!(folderUri, token))) .then(results => results.reduce((first, second) => first.concat(second), []))); } @@ -531,7 +532,7 @@ class Launch extends AbstractLaunch implements ILaunch { return this.configurationService.inspect('launch', { resource: this.workspace.uri }).workspaceFolder; } - openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor | null, created: boolean }> { + openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditor | null, created: boolean }> { const resource = this.uri; let created = false; @@ -539,7 +540,7 @@ class Launch extends AbstractLaunch implements ILaunch { // launch.json not found: create one by collecting launch configs from debugConfigProviders return this.configurationManager.guessDebugger(type).then(adapter => { if (adapter) { - return this.configurationManager.provideDebugConfigurations(this.workspace.uri, adapter.type).then(initialConfigs => { + return this.configurationManager.provideDebugConfigurations(this.workspace.uri, adapter.type, token || CancellationToken.None).then(initialConfigs => { return adapter.getInitialConfigurationContent(initialConfigs); }); } else { diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 907d373337..b0d2c17684 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -57,8 +57,8 @@ export class DebugEditorContribution implements IDebugEditorContribution { private toDispose: lifecycle.IDisposable[]; private hoverWidget: DebugHoverWidget; - private nonDebugHoverPosition: Position; - private hoverRange: Range; + private nonDebugHoverPosition: Position | undefined; + private hoverRange: Range | null = null; private mouseDown = false; private breakpointHintDecoration: string[]; @@ -68,7 +68,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { private exceptionWidget: ExceptionWidget | undefined; - private configurationWidget: FloatingClickWidget; + private configurationWidget: FloatingClickWidget | undefined; constructor( private editor: ICodeEditor, @@ -377,7 +377,11 @@ export class DebugEditorContribution implements IDebugEditorContribution { @memoize private get showHoverScheduler(): RunOnceScheduler { - const scheduler = new RunOnceScheduler(() => this.showHover(this.hoverRange, false), HOVER_DELAY); + const scheduler = new RunOnceScheduler(() => { + if (this.hoverRange) { + this.showHover(this.hoverRange, false); + } + }, HOVER_DELAY); this.toDispose.push(scheduler); return scheduler; @@ -385,7 +389,11 @@ export class DebugEditorContribution implements IDebugEditorContribution { @memoize private get hideHoverScheduler(): RunOnceScheduler { - const scheduler = new RunOnceScheduler(() => this.hoverWidget.hide(), 2 * HOVER_DELAY); + const scheduler = new RunOnceScheduler(() => { + if (!this.hoverWidget.isHovered()) { + this.hoverWidget.hide(); + } + }, 2 * HOVER_DELAY); this.toDispose.push(scheduler); return scheduler; @@ -394,7 +402,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { @memoize private get provideNonDebugHoverScheduler(): RunOnceScheduler { const scheduler = new RunOnceScheduler(() => { - if (this.editor.hasModel()) { + if (this.editor.hasModel() && this.nonDebugHoverPosition) { getHover(this.editor.getModel(), this.nonDebugHoverPosition, CancellationToken.None); } }, HOVER_DELAY); diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorModelManager.ts b/src/vs/workbench/contrib/debug/browser/debugEditorModelManager.ts index 142e24e5f1..2091d69865 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorModelManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorModelManager.ts @@ -36,7 +36,7 @@ export class DebugEditorModelManager implements IWorkbenchContribution { static readonly STICKINESS = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges; private modelDataMap: Map; private toDispose: lifecycle.IDisposable[]; - private ignoreDecorationsChangedEvent: boolean; + private ignoreDecorationsChangedEvent = false; constructor( @IModelService private readonly modelService: IModelService, diff --git a/src/vs/workbench/contrib/debug/browser/debugHelperService.ts b/src/vs/workbench/contrib/debug/browser/debugHelperService.ts deleted file mode 100644 index 07d610ecd4..0000000000 --- a/src/vs/workbench/contrib/debug/browser/debugHelperService.ts +++ /dev/null @@ -1,21 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; -import { IDebugHelperService } from 'vs/workbench/contrib/debug/common/debug'; -import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; - -export class BrowserDebugHelperService implements IDebugHelperService { - - _serviceBrand: ServiceIdentifier; - - createTelemetryService(configurationService: IConfigurationService, args: string[]): TelemetryService | undefined { - return undefined; - } -} - -registerSingleton(IDebugHelperService, BrowserDebugHelperService); \ No newline at end of file diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index 6e0a999ae7..e5318c2b78 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -41,17 +41,16 @@ export class DebugHoverWidget implements IContentWidget { allowEditorOverflow = true; private _isVisible: boolean; - private domNode: HTMLElement; - private tree: AsyncDataTree; + private domNode!: HTMLElement; + private tree!: AsyncDataTree; private showAtPosition: Position | null; private highlightDecorations: string[]; - private complexValueContainer: HTMLElement; - private complexValueTitle: HTMLElement; - private valueContainer: HTMLElement; - private treeContainer: HTMLElement; + private complexValueContainer!: HTMLElement; + private complexValueTitle!: HTMLElement; + private valueContainer!: HTMLElement; + private treeContainer!: HTMLElement; private toDispose: lifecycle.IDisposable[]; - private scrollbar: DomScrollableElement; - private dataSource: DebugHoverDataSource; + private scrollbar!: DomScrollableElement; constructor( private editor: ICodeEditor, @@ -72,10 +71,10 @@ export class DebugHoverWidget implements IContentWidget { this.complexValueTitle = dom.append(this.complexValueContainer, $('.title')); this.treeContainer = dom.append(this.complexValueContainer, $('.debug-hover-tree')); this.treeContainer.setAttribute('role', 'tree'); - this.dataSource = new DebugHoverDataSource(); + const dataSource = new DebugHoverDataSource(); this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, this.treeContainer, new DebugHoverDelegate(), [this.instantiationService.createInstance(VariablesRenderer)], - this.dataSource, { + dataSource, { ariaLabel: nls.localize('treeAriaLabel', "Debug Hover"), accessibilityProvider: new DebugHoverAccessibilityProvider(), mouseSupport: false, @@ -122,6 +121,10 @@ export class DebugHoverWidget implements IContentWidget { })); } + isHovered(): boolean { + return this.domNode.matches(':hover'); + } + isVisible(): boolean { return this._isVisible; } diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts b/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts index 1eace446f5..4e37b988a6 100644 --- a/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts +++ b/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts @@ -77,7 +77,7 @@ export class DebugQuickOpenHandler extends QuickOpenHandler { public static readonly ID = 'workbench.picker.launch'; - private autoFocusIndex: number; + private autoFocusIndex: number | undefined; constructor( @IDebugService private readonly debugService: IDebugService, diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index bce4c8a965..065814633c 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -47,6 +47,7 @@ import { isErrorWithActions, createErrorWithActions } from 'vs/base/common/error import { RunOnceScheduler } from 'vs/base/common/async'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint'; const DEBUG_BREAKPOINTS_ACTIVATED_KEY = 'debug.breakpointactivated'; @@ -87,7 +88,8 @@ export class DebugService implements IDebugService { private inDebugMode: IContextKey; private breakpointsToSendOnResourceSaved: Set; private initializing = false; - private previousState: State; + private previousState: State | undefined; + private initCancellationToken: CancellationTokenSource | undefined; constructor( @IStorageService private readonly storageService: IStorageService, @@ -211,6 +213,10 @@ export class DebugService implements IDebugService { } private endInitializingState() { + if (this.initCancellationToken) { + this.initCancellationToken.cancel(); + this.initCancellationToken = undefined; + } if (this.initializing) { this.initializing = false; this.onStateChange(); @@ -355,8 +361,9 @@ export class DebugService implements IDebugService { } const debuggerThenable: Promise = type ? Promise.resolve() : this.configurationManager.guessDebugger().then(dbgr => { type = dbgr && dbgr.type; }); - return debuggerThenable.then(() => - this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config!).then(config => { + return debuggerThenable.then(() => { + this.initCancellationToken = new CancellationTokenSource(); + return this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config!, this.initCancellationToken.token).then(config => { // a falsy config indicates an aborted launch if (config && config.type) { return this.substituteVariables(launch, config).then(resolvedConfig => { @@ -396,17 +403,17 @@ export class DebugService implements IDebugService { .then(() => false); } - return launch && launch.openConfigFile(false, true).then(() => false); + return launch && launch.openConfigFile(false, true, undefined, this.initCancellationToken ? this.initCancellationToken.token : undefined).then(() => false); }); } if (launch && type && config === null) { // show launch.json only for "config" being "null". - return launch.openConfigFile(false, true, type).then(() => false); + return launch.openConfigFile(false, true, type, this.initCancellationToken ? this.initCancellationToken.token : undefined).then(() => false); } return false; - }) - ); + }); + }); } /** @@ -587,7 +594,8 @@ export class DebugService implements IDebugService { let substitutionThenable: Promise = Promise.resolve(session.configuration); if (launch && needsToSubstitute && unresolved) { - substitutionThenable = this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved.type, unresolved) + this.initCancellationToken = new CancellationTokenSource(); + substitutionThenable = this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved.type, unresolved, this.initCancellationToken.token) .then(resolved => { if (resolved) { // start debugging diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index d2a01b4721..b0572dd077 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -42,7 +42,7 @@ export class DebugSession implements IDebugSession { private sources = new Map(); private threads = new Map(); private rawListeners: IDisposable[] = []; - private fetchThreadsScheduler: RunOnceScheduler; + private fetchThreadsScheduler: RunOnceScheduler | undefined; private repl: ReplModel; private readonly _onDidChangeState = new Emitter(); @@ -169,7 +169,7 @@ export class DebugSession implements IDebugSession { this.raw = new RawDebugSession(debugAdapter, dbgr, this.telemetryService, customTelemetryService, this.windowsService); - return this.raw!.start().then(() => { + return this.raw.start().then(() => { this.registerListeners(); @@ -674,7 +674,10 @@ export class DebugSession implements IDebugSession { if (this.configurationService.getValue('debug').openDebug === 'openOnDebugBreak') { this.viewletService.openViewlet(VIEWLET_ID); } - this.windowService.focusWindow(); + + if (this.configurationService.getValue('debug').focusWindowOnBreak) { + this.windowService.focusWindow(); + } } } }; diff --git a/src/vs/workbench/contrib/debug/browser/debugStatus.ts b/src/vs/workbench/contrib/debug/browser/debugStatus.ts index 2ae12585b4..2f32e2c0df 100644 --- a/src/vs/workbench/contrib/debug/browser/debugStatus.ts +++ b/src/vs/workbench/contrib/debug/browser/debugStatus.ts @@ -13,7 +13,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; export class DebugStatusContribution implements IWorkbenchContribution { - private showInStatusBar: 'never' | 'always' | 'onFirstSessionStart'; + private showInStatusBar!: 'never' | 'always' | 'onFirstSessionStart'; private toDispose: IDisposable[] = []; private entryAccessor: IStatusbarEntryAccessor | undefined; diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index e6aa4553ad..38285884a0 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -55,10 +55,10 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { private activeActions: IAction[]; private updateScheduler: RunOnceScheduler; private debugToolBarMenu: IMenu; - private disposeOnUpdate: IDisposable; + private disposeOnUpdate: IDisposable | undefined; - private isVisible: boolean; - private isBuilt: boolean; + private isVisible = false; + private isBuilt = false; constructor( @INotificationService private readonly notificationService: INotificationService, @@ -126,7 +126,6 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { this.registerListeners(); this.hide(); - this.isBuilt = false; } private registerListeners(): void { diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index b9238d721d..3038575b6e 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -36,12 +36,12 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; export class DebugViewlet extends ViewContainerViewlet { - private startDebugActionViewItem: StartDebugActionViewItem; + private startDebugActionViewItem: StartDebugActionViewItem | undefined; private progressResolve: (() => void) | undefined; - private breakpointView: ViewletPanel; + private breakpointView: ViewletPanel | undefined; private panelListeners = new Map(); - private debugToolBarMenu: IMenu; - private disposeOnTitleUpdate: IDisposable; + private debugToolBarMenu: IMenu | undefined; + private disposeOnTitleUpdate: IDisposable | undefined; constructor( @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, diff --git a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts index c1a59834aa..f7da5631d4 100644 --- a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts @@ -63,8 +63,8 @@ export class ExceptionWidget extends ZoneWidget { this.setCssClass('exception-widget'); // Set the font size and line height to the one from the editor configuration. const fontInfo = this.editor.getConfiguration().fontInfo; - this.container.style.fontSize = `${fontInfo.fontSize}px`; - this.container.style.lineHeight = `${fontInfo.lineHeight}px`; + container.style.fontSize = `${fontInfo.fontSize}px`; + container.style.lineHeight = `${fontInfo.lineHeight}px`; let title = $('.title'); title.textContent = this.exceptionInfo.id ? nls.localize('exceptionThrownWithId', 'Exception has occurred: {0}', this.exceptionInfo.id) : nls.localize('exceptionThrown', 'Exception has occurred.'); @@ -87,11 +87,11 @@ export class ExceptionWidget extends ZoneWidget { protected _doLayout(_heightInPixel: number | undefined, _widthInPixel: number | undefined): void { // Reload the height with respect to the exception text content and relayout it to match the line count. - this.container.style.height = 'initial'; + this.container!.style.height = 'initial'; const lineHeight = this.editor.getConfiguration().lineHeight; const arrowHeight = Math.round(lineHeight / 3); - const computedLinesNumber = Math.ceil((this.container.offsetHeight + arrowHeight) / lineHeight); + const computedLinesNumber = Math.ceil((this.container!.offsetHeight + arrowHeight) / lineHeight); this._relayout(computedLinesNumber); } diff --git a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts new file mode 100644 index 0000000000..51a8d38b4a --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; +import { IDebugHelperService } from 'vs/workbench/contrib/debug/common/debug'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; + +class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient { + + constructor( + @IRemoteAgentService remoteAgentService: IRemoteAgentService, + //@IWindowService windowService: IWindowService, + @IEnvironmentService environmentService: IEnvironmentService + ) { + const connection = remoteAgentService.getConnection(); + + if (!connection) { + throw new Error('Missing agent connection'); + } + + super(connection.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName)); + + this._register(this.onReload(event => { + if (environmentService.isExtensionDevelopment && environmentService.debugExtensionHost.debugId === event.sessionId) { + //windowService.reloadWindow(); + window.location.reload(); + } + })); + this._register(this.onClose(event => { + if (environmentService.isExtensionDevelopment && environmentService.debugExtensionHost.debugId === event.sessionId) { + //this._windowService.closeWindow(); + window.close(); + } + })); + } +} + +registerSingleton(IExtensionHostDebugService, BrowserExtensionHostDebugService); + +class BrowserDebugHelperService implements IDebugHelperService { + + _serviceBrand!: ServiceIdentifier; + + createTelemetryService(configurationService: IConfigurationService, args: string[]): TelemetryService | undefined { + return undefined; + } +} + +registerSingleton(IDebugHelperService, BrowserDebugHelperService); diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index ec02828b98..96c091d828 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -46,7 +46,7 @@ class BaseTreeItem { private _showedMoreThanOne: boolean; private _children = new Map(); - private _source: Source; + private _source: Source | undefined; constructor(private _parent: BaseTreeItem | undefined, private _label: string) { this._showedMoreThanOne = false; @@ -184,7 +184,7 @@ class BaseTreeItem { } // skips intermediate single-child nodes - getSource(): Source { + getSource(): Source | undefined { const child = this.oneChild(); if (child) { return child.getSource(); @@ -381,13 +381,13 @@ class SessionTreeItem extends BaseTreeItem { export class LoadedScriptsView extends ViewletPanel { - private treeContainer: HTMLElement; + private treeContainer!: HTMLElement; private loadedScriptsItemType: IContextKey; - private tree: WorkbenchAsyncDataTree; - private treeLabels: ResourceLabels; - private changeScheduler: RunOnceScheduler; - private treeNeedsRefreshOnVisible: boolean; - private filter: LoadedScriptsFilter; + private tree!: WorkbenchAsyncDataTree; + private treeLabels!: ResourceLabels; + private changeScheduler!: RunOnceScheduler; + private treeNeedsRefreshOnVisible = false; + private filter!: LoadedScriptsFilter; constructor( options: IViewletViewOptions, @@ -635,7 +635,7 @@ class LoadedSciptsAccessibilityProvider implements IAccessibilityProvider { - private filterText: string; + private filterText: string | undefined; setFilter(filterText: string) { this.filterText = filterText; diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index 793c849c93..1d8cc31296 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -37,19 +37,19 @@ interface ILaunchVSCodeArguments { */ export class RawDebugSession { - private allThreadsContinued: boolean; - private _readyForBreakpoints: boolean; + private allThreadsContinued = true; + private _readyForBreakpoints = false; private _capabilities: DebugProtocol.Capabilities; // shutdown - private debugAdapterStopped: boolean; - private inShutdown: boolean; - private terminated: boolean; - private firedAdapterExitEvent: boolean; + private debugAdapterStopped = false; + private inShutdown = false; + private terminated = false; + private firedAdapterExitEvent = false; // telemetry - private startTime: number; - private didReceiveStoppedEvent: boolean; + private startTime = 0; + private didReceiveStoppedEvent = false; // DAP events private readonly _onDidInitialize: Emitter; @@ -78,13 +78,6 @@ export class RawDebugSession { ) { this.debugAdapter = debugAdapter; this._capabilities = Object.create(null); - this._readyForBreakpoints = false; - this.inShutdown = false; - this.debugAdapterStopped = false; - this.firedAdapterExitEvent = false; - this.didReceiveStoppedEvent = false; - - this.allThreadsContinued = true; this._onDidInitialize = new Emitter(); this._onDidStop = new Emitter(); diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index e0e46cf83e..668ed589a8 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -92,18 +92,18 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati private static readonly REPL_INPUT_MAX_HEIGHT = 170; private history: HistoryNavigator; - private tree: WorkbenchAsyncDataTree; - private replDelegate: ReplDelegate; - private container: HTMLElement; - private replInput: CodeEditorWidget; - private replInputContainer: HTMLElement; - private dimension: dom.Dimension; + private tree!: WorkbenchAsyncDataTree; + private replDelegate!: ReplDelegate; + private container!: HTMLElement; + private replInput!: CodeEditorWidget; + private replInputContainer!: HTMLElement; + private dimension!: dom.Dimension; private replInputHeight: number; - private model: ITextModel; - private historyNavigationEnablement: IContextKey; - private scopedInstantiationService: IInstantiationService; - private replElementsChangeListener: IDisposable; - private styleElement: HTMLStyleElement; + private model!: ITextModel; + private historyNavigationEnablement!: IContextKey; + private scopedInstantiationService!: IInstantiationService; + private replElementsChangeListener: IDisposable | undefined; + private styleElement: HTMLStyleElement | undefined; constructor( @IDebugService private readonly debugService: IDebugService, diff --git a/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts index d0e01a0458..7feec60896 100644 --- a/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts +++ b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts @@ -34,7 +34,7 @@ export const STATUS_BAR_DEBUGGING_BORDER = registerColor('statusBar.debuggingBor }, localize('statusBarDebuggingBorder', "Status bar border color separating to the sidebar and editor when a program is being debugged. The status bar is shown in the bottom of the window")); export class StatusBarColorProvider extends Themable implements IWorkbenchContribution { - private styleElement: HTMLStyleElement; + private styleElement: HTMLStyleElement | undefined; constructor( @IThemeService themeService: IThemeService, diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 5b537b3e34..95942512b9 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -39,8 +39,8 @@ export const variableSetEmitter = new Emitter(); export class VariablesView extends ViewletPanel { private onFocusStackFrameScheduler: RunOnceScheduler; - private needsRefresh: boolean; - private tree: WorkbenchAsyncDataTree; + private needsRefresh = false; + private tree!: WorkbenchAsyncDataTree; private savedViewState: IAsyncDataTreeViewState | undefined; constructor( diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index f4c441908a..d913bb4db6 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -36,8 +36,8 @@ const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; export class WatchExpressionsView extends ViewletPanel { private onWatchExpressionsUpdatedScheduler: RunOnceScheduler; - private needsRefresh: boolean; - private tree: WorkbenchAsyncDataTree; + private needsRefresh = false; + private tree!: WorkbenchAsyncDataTree; constructor( options: IViewletViewOptions, diff --git a/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts b/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts index 238064428c..bc53d3b9f1 100644 --- a/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts +++ b/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts @@ -14,9 +14,9 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter { private sequence: number; private pendingRequests = new Map void>(); - private requestCallback: (request: DebugProtocol.Request) => void; - private eventCallback: (request: DebugProtocol.Event) => void; - private messageCallback: (message: DebugProtocol.ProtocolMessage) => void; + private requestCallback: ((request: DebugProtocol.Request) => void) | undefined; + private eventCallback: ((request: DebugProtocol.Event) => void) | undefined; + private messageCallback: ((message: DebugProtocol.ProtocolMessage) => void) | undefined; protected readonly _onError: Emitter; protected readonly _onExit: Emitter; diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 621cbe4dc7..60d6bbfae5 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -25,6 +25,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { TaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { CancellationToken } from 'vs/base/common/cancellation'; export const VIEWLET_ID = 'workbench.view.debug'; export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID); @@ -133,7 +134,7 @@ export function getStateLabel(state: State): string { } } -export class AdapterEndEvent { +export interface AdapterEndEvent { error?: Error; sessionLengthInSeconds: number; emittedStopped: boolean; @@ -440,6 +441,7 @@ export interface IDebugConfiguration { lineHeight: number; wordWrap: boolean; }; + focusWindowOnBreak: boolean; } export interface IGlobalConfig { @@ -556,8 +558,8 @@ export interface IDebuggerContribution extends IPlatformSpecificAdapterContribut export interface IDebugConfigurationProvider { readonly type: string; - resolveDebugConfiguration?(folderUri: uri | undefined, debugConfiguration: IConfig): Promise; - provideDebugConfigurations?(folderUri: uri | undefined): Promise; + resolveDebugConfiguration?(folderUri: uri | undefined, debugConfiguration: IConfig, token: CancellationToken): Promise; + provideDebugConfigurations?(folderUri: uri | undefined, token: CancellationToken): Promise; debugAdapterExecutable?(folderUri: uri | undefined): Promise; // TODO@AW legacy } @@ -609,7 +611,7 @@ export interface IConfigurationManager { registerDebugAdapterDescriptorFactory(debugAdapterDescriptorFactory: IDebugAdapterDescriptorFactory): IDisposable; unregisterDebugAdapterDescriptorFactory(debugAdapterDescriptorFactory: IDebugAdapterDescriptorFactory): void; - resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: any): Promise; + resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: any, token: CancellationToken): Promise; getDebugAdapterDescriptor(session: IDebugSession): Promise; registerDebugAdapterFactory(debugTypes: string[], debugAdapterFactory: IDebugAdapterFactory): IDisposable; @@ -662,7 +664,7 @@ export interface ILaunch { /** * Opens the launch.json file. Creates if it does not exist. */ - openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor | null, created: boolean }>; + openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditor | null, created: boolean }>; } // Debug service interfaces diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 4234173d71..e86b181af2 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -94,8 +94,8 @@ export class ExpressionContainer implements IExpressionContainer { // Use chunks to support variable paging #9537 private static readonly BASE_CHUNK_SIZE = 100; - public valueChanged: boolean; - private _value: string; + public valueChanged = false; + private _value: string = ''; protected children?: Promise; constructor( @@ -201,7 +201,7 @@ export class Expression extends ExpressionContainer implements IExpression { static DEFAULT_VALUE = nls.localize('notAvailable', "not available"); public available: boolean; - public type: string; + public type: string | undefined; constructor(public name: string, id = generateUuid()) { super(undefined, 0, id); diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts similarity index 100% rename from src/vs/workbench/services/extensions/electron-browser/extensionHostDebugService.ts rename to src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts diff --git a/src/vs/workbench/contrib/debug/node/debugAdapter.ts b/src/vs/workbench/contrib/debug/node/debugAdapter.ts index 6602ce0c0f..10f85acca6 100644 --- a/src/vs/workbench/contrib/debug/node/debugAdapter.ts +++ b/src/vs/workbench/contrib/debug/node/debugAdapter.ts @@ -27,9 +27,9 @@ export abstract class StreamDebugAdapter extends AbstractDebugAdapter { private static readonly HEADER_LINESEPARATOR = /\r?\n/; // allow for non-RFC 2822 conforming line separators private static readonly HEADER_FIELDSEPARATOR = /: */; - private outputStream: stream.Writable; - private rawData: Buffer; - private contentLength: number; + private outputStream!: stream.Writable; + private rawData = Buffer.allocUnsafe(0); + private contentLength = -1; constructor() { super(); @@ -145,7 +145,7 @@ export class SocketDebugAdapter extends StreamDebugAdapter { */ export class ExecutableDebugAdapter extends StreamDebugAdapter { - private serverProcess: cp.ChildProcess; + private serverProcess: cp.ChildProcess | undefined; constructor(private adapterExecutable: IDebugAdapterExecutable, private debugType: string, private readonly outputService?: IOutputService) { super(); @@ -266,7 +266,7 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { // processes. Therefore we use TASKKILL.EXE if (platform.isWindows) { return new Promise((c, e) => { - const killer = cp.exec(`taskkill /F /T /PID ${this.serverProcess.pid}`, function (err, stdout, stderr) { + const killer = cp.exec(`taskkill /F /T /PID ${this.serverProcess!.pid}`, function (err, stdout, stderr) { if (err) { return e(err); } diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index a1d0873449..a36e1253ba 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -155,7 +155,7 @@ export class MockSession implements IDebugSession { configuration: IConfig = { type: 'mock', name: 'mock', request: 'launch' }; unresolvedConfiguration: IConfig = { type: 'mock', name: 'mock', request: 'launch' }; state = State.Stopped; - root: IWorkspaceFolder; + root!: IWorkspaceFolder; capabilities: DebugProtocol.Capabilities = {}; getId(): string { @@ -301,9 +301,9 @@ export class MockSession implements IDebugSession { export class MockRawSession { - capabilities: DebugProtocol.Capabilities; - disconnected: boolean; - sessionLengthInSeconds: number; + capabilities: DebugProtocol.Capabilities = {}; + disconnected = false; + sessionLengthInSeconds: number = 0; public readyForBreakpoints = true; public emittedStopped = true; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 167513ee03..cfb92f0ff2 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -50,29 +50,10 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { getDefaultValue } from 'vs/platform/configuration/common/configurationRegistry'; import { isUndefined } from 'vs/base/common/types'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { URI } from 'vs/base/common/uri'; import { IWebviewService, Webview } from 'vs/workbench/contrib/webview/common/webview'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; - -// {{SQL CARBON EDIT}} -import { renderDashboardContributions } from 'sql/workbench/parts/extensions/browser/contributionRenders'; -// {{SQL CARBON EDIT}} - End - -function renderBody(body: string): string { - const styleSheetPath = require.toUrl('./media/markdown.css').replace('file://', 'vscode-resource://'); - return ` - - - - - - - - - ${body} - - `; -} +import { renderDashboardContributions } from 'sql/workbench/parts/extensions/browser/contributionRenders'; // {{SQL CARBON EDIT}} +import { generateUuid } from 'vs/base/common/uuid'; function removeEmbeddedSVGs(documentContent: string): string { const newDocument = new DOMParser().parseFromString(documentContent, 'text/html'); @@ -200,7 +181,7 @@ export class ExtensionEditor extends BaseEditor { @IStorageService storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, - @IWebviewService private readonly webviewService: IWebviewService + @IWebviewService private readonly webviewService: IWebviewService, ) { super(ExtensionEditor.ID, telemetryService, themeService, storageService); this.extensionReadme = null; @@ -565,7 +546,7 @@ export class ExtensionEditor extends BaseEditor { private openMarkdown(cacheResult: CacheResult, noContentCopy: string): Promise { return this.loadContents(() => cacheResult) .then(marked.parse) - .then(renderBody) + .then(content => this.renderBody(content)) .then(removeEmbeddedSVGs) .then(body => { const webviewElement = this.webviewService.createWebview('extensionEditor', @@ -574,9 +555,6 @@ export class ExtensionEditor extends BaseEditor { }, { svgWhiteList: this.extensionsWorkbenchService.allowedBadgeProviders, - localResourceRoots: [ - URI.parse(require.toUrl('./media')) - ] }); webviewElement.mountTo(this.content); this.contentDisposables.add(webviewElement.onDidFocus(() => this.fireOnDidFocus())); @@ -603,6 +581,194 @@ export class ExtensionEditor extends BaseEditor { }); } + private async renderBody(body: string): Promise { + const nonce = generateUuid(); + return ` + + + + + + + + + ${body} + + `; + } + private openReadme(): Promise { return this.openMarkdown(this.extensionReadme!.get(), localize('noReadme', "No README available.")); } diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css index f6fb9afa65..bda60c60e3 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css @@ -61,6 +61,10 @@ padding-left: 5px; } +.extensions-viewlet > .extensions .message-container .severity-icon { + flex-shrink: 0; +} + .extensions-viewlet > .extensions .monaco-list-row > .bookmark { display: inline-block; height: 20px; diff --git a/src/vs/workbench/contrib/extensions/browser/media/markdown.css b/src/vs/workbench/contrib/extensions/browser/media/markdown.css deleted file mode 100644 index 3a970a5097..0000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/markdown.css +++ /dev/null @@ -1,175 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -body { - padding: 10px 20px; - line-height: 22px; -} - -img { - max-width: 100%; - max-height: 100%; -} - -a { - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -a:focus, -input:focus, -select:focus, -textarea:focus { - outline: 1px solid -webkit-focus-ring-color; - outline-offset: -1px; -} - -hr { - border: 0; - height: 2px; - border-bottom: 2px solid; -} - -h1 { - padding-bottom: 0.3em; - line-height: 1.2; - border-bottom-width: 1px; - border-bottom-style: solid; -} - -h1, h2, h3 { - font-weight: normal; -} - -table { - border-collapse: collapse; -} - -table > thead > tr > th { - text-align: left; - border-bottom: 1px solid; -} - -table > thead > tr > th, -table > thead > tr > td, -table > tbody > tr > th, -table > tbody > tr > td { - padding: 5px 10px; -} - -table > tbody > tr + tr > td { - border-top: 1px solid; -} - -blockquote { - margin: 0 7px 0 5px; - padding: 0 16px 0 10px; - border-left-width: 5px; - border-left-style: solid; -} - -code { - font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; - font-size: 14px; - line-height: 19px; -} - -.mac code { - font-size: 12px; - line-height: 18px; -} - -code > div { - padding: 16px; - border-radius: 3px; - overflow: auto; -} - -#scroll-to-top { - position: fixed; - width: 40px; - height: 40px; - right: 25px; - bottom: 25px; - background-color:#444444; - border-radius: 50%; - cursor: pointer; - box-shadow: 1px 1px 1px rgba(0,0,0,.25); - outline: none; - display: flex; - justify-content: center; - align-items: center; -} - -#scroll-to-top:hover { - background-color:#007acc; - box-shadow: 2px 2px 2px rgba(0,0,0,.25); -} - -body.vscode-light #scroll-to-top { - background-color: #949494; -} - -body.vscode-high-contrast #scroll-to-top:hover { - background-color: #007acc; -} - -body.vscode-high-contrast #scroll-to-top { - background-color: black; - border: 2px solid #6fc3df; - box-shadow: none; -} -body.vscode-high-contrast #scroll-to-top:hover { - background-color: #007acc; -} - -#scroll-to-top span.icon::before { - content: ""; - display: block; - /* Chevron up icon */ - background:url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjIuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxNiAxNiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTYgMTY7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbDojRkZGRkZGO30KCS5zdDF7ZmlsbDpub25lO30KPC9zdHlsZT4KPHRpdGxlPnVwY2hldnJvbjwvdGl0bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik04LDUuMWwtNy4zLDcuM0wwLDExLjZsOC04bDgsOGwtMC43LDAuN0w4LDUuMXoiLz4KPHJlY3QgY2xhc3M9InN0MSIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2Ii8+Cjwvc3ZnPgo='); - width: 16px; - height: 16px; -} - -/** Theming */ -.vscode-light code > div { - background-color: rgba(220, 220, 220, 0.4); -} - -.vscode-dark code > div { - background-color: rgba(10, 10, 10, 0.4); -} - -.vscode-high-contrast code > div { - background-color: rgb(0, 0, 0); -} - -.vscode-high-contrast h1 { - border-color: rgb(0, 0, 0); -} - -.vscode-light table > thead > tr > th { - border-color: rgba(0, 0, 0, 0.69); -} - -.vscode-dark table > thead > tr > th { - border-color: rgba(255, 255, 255, 0.69); -} - -.vscode-light h1, -.vscode-light hr, -.vscode-light table > tbody > tr + tr > td { - border-color: rgba(0, 0, 0, 0.18); -} - -.vscode-dark h1, -.vscode-dark hr, -.vscode-dark table > tbody > tr + tr > td { - border-color: rgba(255, 255, 255, 0.18); -} diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts index 24bee1e0a5..ac6e44411d 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; -import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionHostProfile, ProfileSession, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { Disposable, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -23,7 +23,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; export class ExtensionHostProfileService extends Disposable implements IExtensionHostProfileService { - _serviceBrand: ServiceIdentifier; + _serviceBrand: any; private readonly _onDidChangeState: Emitter = this._register(new Emitter()); public readonly onDidChangeState: Event = this._onDidChangeState.event; @@ -34,7 +34,7 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio private readonly _unresponsiveProfiles = new Map(); private _profile: IExtensionHostProfile | null; private _profileSession: ProfileSession | null; - private _state: ProfileSessionState; + private _state: ProfileSessionState = ProfileSessionState.None; private profilingStatusBarIndicator: IStatusbarEntryAccessor | undefined; private readonly profilingStatusBarIndicatorLabelUpdater = this._register(new MutableDisposable()); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions.ts index ce5ed9193d..ab6f972288 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions.ts @@ -19,9 +19,9 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; abstract class RepoInfo { - readonly base: string; - readonly owner: string; - readonly repo: string; + abstract get base(): string; + abstract get owner(): string; + abstract get repo(): string; static fromExtension(desc: IExtensionDescription): RepoInfo | undefined { diff --git a/src/vs/workbench/contrib/feedback/browser/feedback.ts b/src/vs/workbench/contrib/feedback/browser/feedback.ts index bac2a038fd..76ad9b4801 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedback.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedback.ts @@ -46,13 +46,13 @@ export class FeedbackDropdown extends Dropdown { private readonly feedbackDelegate: IFeedbackDelegate; - private feedbackForm: HTMLFormElement | null; - private feedbackDescriptionInput: HTMLTextAreaElement | null; - private smileyInput: HTMLElement | null; - private frownyInput: HTMLElement | null; - private sendButton: Button; - private hideButton: HTMLInputElement; - private remainingCharacterCount: HTMLElement; + private feedbackForm: HTMLFormElement | null = null; + private feedbackDescriptionInput: HTMLTextAreaElement | null = null; + private smileyInput: HTMLElement | null = null; + private frownyInput: HTMLElement | null = null; + private sendButton: Button | null = null; + private hideButton: HTMLInputElement | null = null; + private remainingCharacterCount: HTMLElement | null = null; private requestFeatureLink: string | undefined; @@ -253,7 +253,7 @@ export class FeedbackDropdown extends Dropdown { // Checkbox: Hide Feedback Smiley const hideButtonContainer = dom.append(buttonsContainer, dom.$('div.hide-button-container')); - this.hideButton = dom.append(hideButtonContainer, dom.$('input.hide-button')); + this.hideButton = dom.append(hideButtonContainer, dom.$('input.hide-button')) as HTMLInputElement; this.hideButton.type = 'checkbox'; this.hideButton.checked = true; this.hideButton.id = 'hide-button'; @@ -316,7 +316,7 @@ export class FeedbackDropdown extends Dropdown { } private updateCharCountText(): void { - if (this.feedbackDescriptionInput) { + if (this.feedbackDescriptionInput && this.remainingCharacterCount && this.sendButton) { this.remainingCharacterCount.innerText = this.getCharCountText(this.feedbackDescriptionInput.value.length); this.sendButton.enabled = this.feedbackDescriptionInput.value.length > 0; } @@ -434,4 +434,4 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { if (linkColor) { collector.addRule(`.monaco-workbench .feedback-form .content .channels a { color: ${linkColor}; }`); } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts index 26b2314a47..e725cab005 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts @@ -47,8 +47,8 @@ class TwitterFeedbackService implements IFeedbackDelegate { } export class FeedbackStatusbarConribution extends Disposable implements IWorkbenchContribution { - private dropdown: FeedbackDropdown; - private entry: IStatusbarEntryAccessor; + private dropdown: FeedbackDropdown | undefined; + private entry: IStatusbarEntryAccessor | undefined; constructor( @IStatusbarService statusbarService: IStatusbarService, @@ -72,15 +72,17 @@ export class FeedbackStatusbarConribution extends Disposable implements IWorkben this.dropdown = this._register(this.instantiationService.createInstance(FeedbackDropdown, statusContainr.getElementsByClassName('octicon').item(0), { contextViewProvider: this.contextViewService, feedbackService: this.instantiationService.createInstance(TwitterFeedbackService), - onFeedbackVisibilityChange: visible => this.entry.update(this.getStatusEntry(visible)) + onFeedbackVisibilityChange: visible => this.entry!.update(this.getStatusEntry(visible)) })); } } - if (!this.dropdown.isVisible()) { - this.dropdown.show(); - } else { - this.dropdown.hide(); + if (this.dropdown) { + if (!this.dropdown.isVisible()) { + this.dropdown.show(); + } else { + this.dropdown.hide(); + } } } @@ -92,4 +94,4 @@ export class FeedbackStatusbarConribution extends Disposable implements IWorkben }; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index e1feb2da01..5bc92050f4 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -33,7 +33,7 @@ import { QueryInput } from 'sql/workbench/parts/query/common/queryInput'; export class FileEditorTracker extends Disposable implements IWorkbenchContribution { - private closeOnFileDelete: boolean; + private closeOnFileDelete: boolean | undefined; private modelLoadQueue = new ResourceQueue(); private activeOutOfWorkspaceWatchers = new ResourceMap(); diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index 10e7df78f3..743cd9a488 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -40,7 +40,7 @@ export class TextFileEditor extends BaseTextEditor { static readonly ID = TEXT_FILE_EDITOR_ID; - private restoreViewState: boolean; + private restoreViewState: boolean | undefined; private readonly groupListener = this._register(new MutableDisposable()); constructor( diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index e6db85cf5c..b883d0c4d0 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -37,7 +37,7 @@ import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/ export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution { - private openEditorsVisibleContextKey: IContextKey; + private openEditorsVisibleContextKey!: IContextKey; constructor( @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index fd1c8d27a8..4944b2e498 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ToggleAutoSaveAction, GlobalNewUntitledFileAction, ShowOpenedFileInNewWindow, FocusFilesExplorer, GlobalCompareResourcesAction, SaveAllAction, ShowActiveFileInExplorer, CollapseExplorerView, RefreshExplorerView, CompareWithClipboardAction, NEW_FILE_COMMAND_ID, NEW_FILE_LABEL, NEW_FOLDER_COMMAND_ID, NEW_FOLDER_LABEL, TRIGGER_RENAME_LABEL, MOVE_FILE_TO_TRASH_LABEL, COPY_FILE_LABEL, PASTE_FILE_LABEL, FileCopiedContext, renameHandler, moveFileToTrashHandler, copyFileHandler, pasteFileHandler, deleteFileHandler, cutFileHandler, DOWNLOAD_COMMAND_ID, GlobalNewUntitledPlainFileAction } from 'vs/workbench/contrib/files/browser/fileActions'; +import { ToggleAutoSaveAction, GlobalNewUntitledFileAction, ShowOpenedFileInNewWindow, FocusFilesExplorer, GlobalCompareResourcesAction, SaveAllAction, ShowActiveFileInExplorer, CollapseExplorerView, RefreshExplorerView, CompareWithClipboardAction, NEW_FILE_COMMAND_ID, NEW_FILE_LABEL, NEW_FOLDER_COMMAND_ID, NEW_FOLDER_LABEL, TRIGGER_RENAME_LABEL, MOVE_FILE_TO_TRASH_LABEL, COPY_FILE_LABEL, PASTE_FILE_LABEL, FileCopiedContext, renameHandler, moveFileToTrashHandler, copyFileHandler, pasteFileHandler, deleteFileHandler, cutFileHandler, DOWNLOAD_COMMAND_ID, GlobalNewUntitledPlainFileAction, openFilePreserveFocusHandler } from 'vs/workbench/contrib/files/browser/fileActions'; import { revertLocalChangesCommand, acceptLocalChangesCommand, CONFLICT_RESOLUTION_CONTEXT } from 'vs/workbench/contrib/files/browser/saveErrorHandler'; import { SyncActionDescriptor, MenuId, MenuRegistry, ILocalizedString } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; @@ -188,6 +188,14 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'filesExplorer.openFilePreserveFocus', + weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus, + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerFolderContext.toNegated()), + primary: KeyCode.Space, + handler: openFilePreserveFocusHandler +}); + const copyPathCommand = { id: COPY_PATH_COMMAND_ID, title: nls.localize('copyPath', "Copy Path") diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 9cdf6d5b6e..b14de369fc 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -15,7 +15,7 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import * as strings from 'vs/base/common/strings'; import { Action } from 'vs/base/common/actions'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { VIEWLET_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; +import { VIEWLET_ID, IExplorerService, IFilesConfiguration } from 'vs/workbench/contrib/files/common/files'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IFileService, AutoSaveConfiguration } from 'vs/platform/files/common/files'; import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; @@ -360,7 +360,7 @@ function containsBothDirectoryAndFile(distinctElements: ExplorerItem[]): boolean } -export function findValidPasteFileTarget(targetFolder: ExplorerItem, fileToPaste: { resource: URI, isDirectory?: boolean, allowOverwrite: boolean }): URI { +export function findValidPasteFileTarget(targetFolder: ExplorerItem, fileToPaste: { resource: URI, isDirectory?: boolean, allowOverwrite: boolean }, incrementalNaming: 'simple' | 'smart'): URI { let name = resources.basenameOrAuthority(fileToPaste.resource); let candidate = resources.joinPath(targetFolder.resource, name); @@ -369,37 +369,104 @@ export function findValidPasteFileTarget(targetFolder: ExplorerItem, fileToPaste break; } - name = incrementFileName(name, !!fileToPaste.isDirectory); + name = incrementFileName(name, !!fileToPaste.isDirectory, incrementalNaming); candidate = resources.joinPath(targetFolder.resource, name); } return candidate; } -export function incrementFileName(name: string, isFolder: boolean): string { - let namePrefix = name; - let extSuffix = ''; - if (!isFolder) { - extSuffix = extname(name); - namePrefix = basename(name, extSuffix); +export function incrementFileName(name: string, isFolder: boolean, incrementalNaming: 'simple' | 'smart'): string { + if (incrementalNaming === 'simple') { + let namePrefix = name; + let extSuffix = ''; + if (!isFolder) { + extSuffix = extname(name); + namePrefix = basename(name, extSuffix); + } + + // name copy 5(.txt) => name copy 6(.txt) + // name copy(.txt) => name copy 2(.txt) + const suffixRegex = /^(.+ copy)( \d+)?$/; + if (suffixRegex.test(namePrefix)) { + return namePrefix.replace(suffixRegex, (match, g1?, g2?) => { + let number = (g2 ? parseInt(g2) : 1); + return number === 0 + ? `${g1}` + : (number < Constants.MAX_SAFE_SMALL_INTEGER + ? `${g1} ${number + 1}` + : `${g1}${g2} copy`); + }) + extSuffix; + } + + // name(.txt) => name copy(.txt) + return `${namePrefix} copy${extSuffix}`; } - // name copy 5(.txt) => name copy 6(.txt) - // name copy(.txt) => name copy 2(.txt) - const suffixRegex = /^(.+ copy)( \d+)?$/; - if (suffixRegex.test(namePrefix)) { - return namePrefix.replace(suffixRegex, (match, g1?, g2?) => { - let number = (g2 ? parseInt(g2) : 1); - return number === 0 - ? `${g1}` - : (number < Constants.MAX_SAFE_SMALL_INTEGER - ? `${g1} ${number + 1}` - : `${g1}${g2} copy`); - }) + extSuffix; + const separators = '[\\.\\-_]'; + const maxNumber = Constants.MAX_SAFE_SMALL_INTEGER; + + // file.1.txt=>file.2.txt + let suffixFileRegex = RegExp('(.*' + separators + ')(\\d+)(\\..*)$'); + if (!isFolder && name.match(suffixFileRegex)) { + return name.replace(suffixFileRegex, (match, g1?, g2?, g3?) => { + let number = parseInt(g2); + return number < maxNumber + ? g1 + strings.pad(number + 1, g2.length) + g3 + : strings.format('{0}{1}.1{2}', g1, g2, g3); + }); } - // name(.txt) => name copy(.txt) - return `${namePrefix} copy${extSuffix}`; + // 1.file.txt=>2.file.txt + let prefixFileRegex = RegExp('(\\d+)(' + separators + '.*)(\\..*)$'); + if (!isFolder && name.match(prefixFileRegex)) { + return name.replace(prefixFileRegex, (match, g1?, g2?, g3?) => { + let number = parseInt(g1); + return number < maxNumber + ? strings.pad(number + 1, g1.length) + g2 + g3 + : strings.format('{0}{1}.1{2}', g1, g2, g3); + }); + } + + // 1.txt=>2.txt + let prefixFileNoNameRegex = RegExp('(\\d+)(\\..*)$'); + if (!isFolder && name.match(prefixFileNoNameRegex)) { + return name.replace(prefixFileNoNameRegex, (match, g1?, g2?) => { + let number = parseInt(g1); + return number < maxNumber + ? strings.pad(number + 1, g1.length) + g2 + : strings.format('{0}.1{1}', g1, g2); + }); + } + + // file.txt=>file.1.txt + const lastIndexOfDot = name.lastIndexOf('.'); + if (!isFolder && lastIndexOfDot >= 0) { + return strings.format('{0}.1{1}', name.substr(0, lastIndexOfDot), name.substr(lastIndexOfDot)); + } + + // folder.1=>folder.2 + if (isFolder && name.match(/(\d+)$/)) { + return name.replace(/(\d+)$/, (match: string, ...groups: any[]) => { + let number = parseInt(groups[0]); + return number < maxNumber + ? strings.pad(number + 1, groups[0].length) + : strings.format('{0}.1', groups[0]); + }); + } + + // 1.folder=>2.folder + if (isFolder && name.match(/^(\d+)/)) { + return name.replace(/^(\d+)(.*)$/, (match: string, ...groups: any[]) => { + let number = parseInt(groups[0]); + return number < maxNumber + ? strings.pad(number + 1, groups[0].length) + groups[1] + : strings.format('{0}{1}.1', groups[0], groups[1]); + }); + } + + // file/folder=>file.1/folder.1 + return strings.format('{0}.1', name); } // Global Compare with @@ -1038,6 +1105,7 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { const textFileService = accessor.get(ITextFileService); const notificationService = accessor.get(INotificationService); const editorService = accessor.get(IEditorService); + const configurationService = accessor.get(IConfigurationService); if (listService.lastFocusedList) { const explorerContext = getContext(listService.lastFocusedList); @@ -1062,7 +1130,8 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { target = element.isDirectory ? element : element.parent!; } - const targetFile = findValidPasteFileTarget(target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwrite: pasteShouldMove }); + const incrementalNaming = configurationService.getValue().explorer.incrementalNaming; + const targetFile = findValidPasteFileTarget(target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwrite: pasteShouldMove }, incrementalNaming); // Move/Copy File if (pasteShouldMove) { @@ -1091,3 +1160,19 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { } } }; + +export const openFilePreserveFocusHandler = async (accessor: ServicesAccessor) => { + const listService = accessor.get(IListService); + const editorService = accessor.get(IEditorService); + + if (listService.lastFocusedList) { + const explorerContext = getContext(listService.lastFocusedList); + if (explorerContext.stat) { + const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat]; + await editorService.openEditors(stats.filter(s => !s.isDirectory).map(s => ({ + resource: s.resource, + options: { preserveFocus: true } + }))); + } + } +}; diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 60f604b2ae..43dae5fd81 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -38,7 +38,6 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IEditorService, SIDE_GROUP, IResourceEditorReplacement } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ILabelService } from 'vs/platform/label/common/label'; -import { onUnexpectedError } from 'vs/base/common/errors'; import { basename, toLocalResource, joinPath } from 'vs/base/common/resources'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -111,7 +110,7 @@ export const newWindowCommand = (accessor: ServicesAccessor, options?: INewWindo }; // {{SQL CARBON EDIT}} -function save( +async function save( resource: URI | null, isSaveAs: boolean, options: ISaveOptions | undefined, @@ -123,109 +122,123 @@ function save( queryEditorService: IQueryEditorService, environmentService: IWorkbenchEnvironmentService ): Promise { + if (!resource || (!fileService.canHandleResource(resource) && resource.scheme !== Schemas.untitled)) { + return; // save is not supported + } - function ensureForcedSave(options?: ISaveOptions): ISaveOptions { - if (!options) { - options = { force: true }; - } else { - options.force = true; - } - - return options; + // Save As (or Save untitled with associated path) + if (isSaveAs || resource.scheme === Schemas.untitled) { + return doSaveAs(resource, isSaveAs, options, editorService, fileService, untitledEditorService, textFileService, editorGroupService, queryEditorService, environmentService); // {{SQL CARBON EDIT}} add paramater } if (resource && (fileService.canHandleResource(resource) || resource.scheme === Schemas.untitled)) { // {{SQL CARBON EDIT}} let editorInput = editorService.activeEditor; if (editorInput instanceof EditorInput && !(editorInput).savingSupported) { - return Promise.resolve(false); + return; } - - // Save As (or Save untitled with associated path) - if (isSaveAs || resource.scheme === Schemas.untitled) { - let encodingOfSource: string | undefined; - if (resource.scheme === Schemas.untitled) { - encodingOfSource = untitledEditorService.getEncoding(resource); - } else if (fileService.canHandleResource(resource)) { - const textModel = textFileService.models.get(resource); - encodingOfSource = textModel && textModel.getEncoding(); // text model can be null e.g. if this is a binary file! - } - - let viewStateOfSource: IEditorViewState | null; - const activeTextEditorWidget = getCodeEditor(editorService.activeTextEditorWidget); - if (activeTextEditorWidget) { - const activeResource = toResource(editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); - if (activeResource && (fileService.canHandleResource(activeResource) || resource.scheme === Schemas.untitled) && activeResource.toString() === resource.toString()) { - viewStateOfSource = activeTextEditorWidget.saveViewState(); - } - } - - // Special case: an untitled file with associated path gets saved directly unless "saveAs" is true - let savePromise: Promise; - if (!isSaveAs && resource.scheme === Schemas.untitled && untitledEditorService.hasAssociatedFilePath(resource)) { - savePromise = textFileService.save(resource, options).then(result => { - if (result) { - return toLocalResource(resource, environmentService.configuration.remoteAuthority); - } - - return undefined; - }); - } - - // Otherwise, really "Save As..." - else { - - // Force a change to the file to trigger external watchers if any - // fixes https://github.com/Microsoft/vscode/issues/59655 - options = ensureForcedSave(options); - - savePromise = textFileService.saveAs(resource, undefined, options); - } - - return savePromise.then(target => { - if (!target || target.toString() === resource.toString()) { - return false; // save canceled or same resource used - } - - const replacement: IResourceInput = { - resource: target, - encoding: encodingOfSource, - options: { - pinned: true, - viewState: viewStateOfSource || undefined - } - }; - - return Promise.all(editorGroupService.groups.map(g => - editorService.replaceEditors([{ - editor: { resource }, - replacement - }], g))).then(() => { - // {{SQL CARBON EDIT}} - queryEditorService.onSaveAsCompleted(resource, target); - return true; - }); - - }); - } - - // Pin the active editor if we are saving it - const activeControl = editorService.activeControl; - const activeEditorResource = activeControl && activeControl.input && activeControl.input.getResource(); - if (activeControl && activeEditorResource && activeEditorResource.toString() === resource.toString()) { - activeControl.group.pinEditor(activeControl.input); - } - - // Just save (force a change to the file to trigger external watchers if any) - options = ensureForcedSave(options); - - return textFileService.save(resource, options); } - return Promise.resolve(false); + // Save + return doSave(resource, options, editorService, textFileService); } -function saveAll(saveAllArguments: any, editorService: IEditorService, untitledEditorService: IUntitledEditorService, +async function doSaveAs( + resource: URI, + isSaveAs: boolean, + options: ISaveOptions | undefined, + editorService: IEditorService, + fileService: IFileService, + untitledEditorService: IUntitledEditorService, + textFileService: ITextFileService, + editorGroupService: IEditorGroupsService, + queryEditorService: IQueryEditorService, + environmentService: IWorkbenchEnvironmentService +): Promise { + let viewStateOfSource: IEditorViewState | null = null; + const activeTextEditorWidget = getCodeEditor(editorService.activeTextEditorWidget); + if (activeTextEditorWidget) { + const activeResource = toResource(editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); + if (activeResource && (fileService.canHandleResource(activeResource) || resource.scheme === Schemas.untitled) && activeResource.toString() === resource.toString()) { + viewStateOfSource = activeTextEditorWidget.saveViewState(); + } + } + + // Special case: an untitled file with associated path gets saved directly unless "saveAs" is true + let target: URI | undefined; + if (!isSaveAs && resource.scheme === Schemas.untitled && untitledEditorService.hasAssociatedFilePath(resource)) { + const result = await textFileService.save(resource, options); + if (result) { + target = toLocalResource(resource, environmentService.configuration.remoteAuthority); + } + } + + // Otherwise, really "Save As..." + else { + + // Force a change to the file to trigger external watchers if any + // fixes https://github.com/Microsoft/vscode/issues/59655 + options = ensureForcedSave(options); + + target = await textFileService.saveAs(resource, undefined, options); + } + + if (!target || target.toString() === resource.toString()) { + return false; // save canceled or same resource used + } + + const replacement: IResourceInput = { + resource: target, + options: { + pinned: true, + viewState: viewStateOfSource || undefined + } + }; + + await Promise.all(editorGroupService.groups.map(group => + editorService.replaceEditors([{ + editor: { resource }, + replacement + }], group))).then(() => { + // {{SQL CARBON EDIT}} + queryEditorService.onSaveAsCompleted(resource, target); + return true; + }); + + return true; +} + +async function doSave( + resource: URI, + options: ISaveOptions | undefined, + editorService: IEditorService, + textFileService: ITextFileService +): Promise { + + // Pin the active editor if we are saving it + const activeControl = editorService.activeControl; + const activeEditorResource = activeControl && activeControl.input && activeControl.input.getResource(); + if (activeControl && activeEditorResource && activeEditorResource.toString() === resource.toString()) { + activeControl.group.pinEditor(activeControl.input); + } + + // Just save (force a change to the file to trigger external watchers if any) + options = ensureForcedSave(options); + + return textFileService.save(resource, options); +} + +function ensureForcedSave(options?: ISaveOptions): ISaveOptions { + if (!options) { + options = { force: true }; + } else { + options.force = true; + } + + return options; +} + +async function saveAll(saveAllArguments: any, editorService: IEditorService, untitledEditorService: IUntitledEditorService, textFileService: ITextFileService, editorGroupService: IEditorGroupsService): Promise { // Store some properties per untitled file to restore later after save is completed @@ -255,30 +268,30 @@ function saveAll(saveAllArguments: any, editorService: IEditorService, untitledE }); // Save all - return textFileService.saveAll(saveAllArguments).then(result => { - groupIdToUntitledResourceInput.forEach((inputs, groupId) => { - // {{SQL CARBON EDIT}} - // Update untitled resources to the saved ones, so we open the proper files + const result = await textFileService.saveAll(saveAllArguments); - let replacementPairs: IResourceEditorReplacement[] = []; - inputs.forEach(i => { - const targetResult = result.results.filter(r => r.success && r.source.toString() === i.resource.toString()).pop(); - if (targetResult && targetResult.target) { - //i.resource = targetResult.target; - let editor = i; - const replacement: IResourceInput = { - resource: targetResult.target, - encoding: i.encoding, - options: { - pinned: true, - viewState: undefined - } - }; - replacementPairs.push({ editor: editor, replacement: replacement }); - } - }); - editorService.replaceEditors(replacementPairs, groupId); + // Update untitled resources to the saved ones, so we open the proper files + groupIdToUntitledResourceInput.forEach((inputs, groupId) => { + // {{SQL CARBON EDIT}} Update untitled resources to the saved ones, so we open the proper files + const replacementPairs: IResourceEditorReplacement[] = []; + inputs.forEach(i => { + const targetResult = result.results.filter(r => r.success && r.source.toString() === i.resource.toString()).pop(); + if (targetResult && targetResult.target) { + // i.resource = targetResult.target;let editor = i; + const editor = i; + const replacement: IResourceInput = { + resource: targetResult.target, + encoding: i.encoding, + options: { + pinned: true, + viewState: undefined + } + }; + replacementPairs.push({ editor: editor, replacement: replacement }); + } }); + + editorService.replaceEditors(replacementPairs, groupId); }); } @@ -286,7 +299,7 @@ function saveAll(saveAllArguments: any, editorService: IEditorService, untitledE CommandsRegistry.registerCommand({ id: REVERT_FILE_COMMAND_ID, - handler: (accessor, resource: URI | object) => { + handler: async (accessor, resource: URI | object) => { const editorService = accessor.get(IEditorService); const textFileService = accessor.get(ITextFileService); const notificationService = accessor.get(INotificationService); @@ -294,12 +307,12 @@ CommandsRegistry.registerCommand({ .filter(resource => resource.scheme !== Schemas.untitled); if (resources.length) { - return textFileService.revertAll(resources, { force: true }).then(undefined, error => { + try { + await textFileService.revertAll(resources, { force: true }); + } catch (error) { notificationService.error(nls.localize('genericRevertError', "Failed to revert '{0}': {1}", resources.map(r => basename(r)).join(', '), toErrorMessage(error, false))); - }); + } } - - return Promise.resolve(true); } }); @@ -310,7 +323,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ mac: { primary: KeyMod.WinCtrl | KeyCode.Enter }, - id: OPEN_TO_SIDE_COMMAND_ID, handler: (accessor, resource: URI | object) => { + id: OPEN_TO_SIDE_COMMAND_ID, handler: async (accessor, resource: URI | object) => { const editorService = accessor.get(IEditorService); const listService = accessor.get(IListService); const fileService = accessor.get(IFileService); @@ -318,16 +331,13 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ // Set side input if (resources.length) { - return fileService.resolveAll(resources.map(resource => ({ resource }))).then(resolved => { - const editors = resolved.filter(r => r.stat && r.success && !r.stat.isDirectory).map(r => ({ - resource: r.stat!.resource - })); + const resolved = await fileService.resolveAll(resources.map(resource => ({ resource }))); + const editors = resolved.filter(r => r.stat && r.success && !r.stat.isDirectory).map(r => ({ + resource: r.stat!.resource + })); - return editorService.openEditors(editors, SIDE_GROUP); - }); + await editorService.openEditors(editors, SIDE_GROUP); } - - return Promise.resolve(true); } }); @@ -422,7 +432,7 @@ CommandsRegistry.registerCommand({ editorService.openEditor({ leftResource: globalResourceToCompare, rightResource - }).then(undefined, onUnexpectedError); + }); } } }); @@ -521,27 +531,28 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ CommandsRegistry.registerCommand({ id: REVEAL_IN_EXPLORER_COMMAND_ID, - handler: (accessor, resource: URI | object) => { + handler: async (accessor, resource: URI | object) => { const viewletService = accessor.get(IViewletService); const contextService = accessor.get(IWorkspaceContextService); const explorerService = accessor.get(IExplorerService); const uri = getResourceForCommand(resource, accessor.get(IListService), accessor.get(IEditorService)); - viewletService.openViewlet(VIEWLET_ID, false).then((viewlet: ExplorerViewlet) => { - if (uri && contextService.isInsideWorkspace(uri)) { - const explorerView = viewlet.getExplorerView(); - if (explorerView) { - explorerView.setExpanded(true); - explorerService.select(uri, true).then(() => explorerView.focus(), onUnexpectedError); - } - } else { - const openEditorsView = viewlet.getOpenEditorsView(); - if (openEditorsView) { - openEditorsView.setExpanded(true); - openEditorsView.focus(); - } + const viewlet = await viewletService.openViewlet(VIEWLET_ID, false) as ExplorerViewlet; + + if (uri && contextService.isInsideWorkspace(uri)) { + const explorerView = viewlet.getExplorerView(); + if (explorerView) { + explorerView.setExpanded(true); + await explorerService.select(uri, true); + explorerView.focus(); } - }); + } else { + const openEditorsView = viewlet.getOpenEditorsView(); + if (openEditorsView) { + openEditorsView.setExpanded(true); + openEditorsView.focus(); + } + } } }); @@ -610,7 +621,7 @@ CommandsRegistry.registerCommand({ CommandsRegistry.registerCommand({ id: SAVE_ALL_IN_GROUP_COMMAND_ID, - handler: (accessor, resource: URI | object, editorContext: IEditorCommandsContext) => { + handler: (accessor, _: URI | object, editorContext: IEditorCommandsContext) => { const contexts = getMultiSelectedEditorContexts(editorContext, accessor.get(IListService), accessor.get(IEditorGroupsService)); const editorGroupService = accessor.get(IEditorGroupsService); let saveAllArg: any; diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 383b467e17..79db1329f4 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -428,6 +428,15 @@ configurationRegistry.registerConfiguration({ description: nls.localize('explorer.decorations.badges', "Controls whether file decorations should use badges."), default: true }, + 'explorer.incrementalNaming': { + enum: ['simple', 'smart'], + enumDescriptions: [ + nls.localize('simple', "Appends the word \"copy\" at the end of the duplicated name potentially followed by a number"), + nls.localize('smart', "Adds a number at the end of the duplicated name. If some number is already part of the name, tries to increase that number") + ], + description: nls.localize('explorer.incrementalNaming', "Controls what naming strategy to use when a giving a new name to a duplicated explorer item on paste."), + default: 'simple' + } } }); diff --git a/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts index 398052a9b8..104c35d8af 100644 --- a/src/vs/workbench/contrib/files/browser/views/emptyView.ts +++ b/src/vs/workbench/contrib/files/browser/views/emptyView.ts @@ -32,9 +32,9 @@ export class EmptyView extends ViewletPanel { static readonly ID: string = 'workbench.explorer.emptyView'; static readonly NAME = nls.localize('noWorkspace', "No Folder Opened"); - private button: Button; - private messageElement: HTMLElement; - private titleElement: HTMLElement; + private button!: Button; + private messageElement!: HTMLElement; + private titleElement!: HTMLElement; constructor( options: IViewletViewOptions, diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index eac1fbc15b..9af8310636 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -55,8 +55,8 @@ export class ExplorerView extends ViewletPanel { static readonly ID: string = 'workbench.explorer.fileView'; static readonly TREE_VIEW_STATE_STORAGE_KEY: string = 'workbench.explorer.treeViewState'; - private tree: WorkbenchAsyncDataTree; - private filter: FilesFilter; + private tree!: WorkbenchAsyncDataTree; + private filter!: FilesFilter; private resourceContext: ResourceContextKey; private folderContext: IContextKey; @@ -66,7 +66,7 @@ export class ExplorerView extends ViewletPanel { // Refresh is needed on the initial explorer open private shouldRefresh = true; - private dragHandler: DelayedDragHandler; + private dragHandler!: DelayedDragHandler; private autoReveal = false; constructor( @@ -301,7 +301,8 @@ export class ExplorerView extends ViewletPanel { filter: this.filter, sorter: this.instantiationService.createInstance(FileSorter), dnd: this.instantiationService.createInstance(FileDragAndDrop), - autoExpandSingleChildren: true + autoExpandSingleChildren: true, + additionalScrollHeight: ExplorerDelegate.ITEM_HEIGHT }); this._register(this.tree); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 11c289390f..35a5c0a1e2 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -49,7 +49,7 @@ import { FuzzyScore, createMatches } from 'vs/base/common/filters'; export class ExplorerDelegate implements IListVirtualDelegate { - private static readonly ITEM_HEIGHT = 22; + static readonly ITEM_HEIGHT = 22; getHeight(element: ExplorerItem): number { return ExplorerDelegate.ITEM_HEIGHT; @@ -440,7 +440,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { private static readonly CONFIRM_DND_SETTING_KEY = 'explorer.confirmDragAndDrop'; private toDispose: IDisposable[]; - private dropEnabled: boolean; + private dropEnabled = false; constructor( @INotificationService private notificationService: INotificationService, @@ -808,8 +808,8 @@ export class FileDragAndDrop implements ITreeDragAndDrop { private doHandleExplorerDrop(source: ExplorerItem, target: ExplorerItem, isCopy: boolean): Promise { // Reuse duplicate action if user copies if (isCopy) { - - return this.fileService.copy(source.resource, findValidPasteFileTarget(target, { resource: source.resource, isDirectory: source.isDirectory, allowOverwrite: false })).then(stat => { + const incrementalNaming = this.configurationService.getValue().explorer.incrementalNaming; + return this.fileService.copy(source.resource, findValidPasteFileTarget(target, { resource: source.resource, isDirectory: source.isDirectory, allowOverwrite: false }, incrementalNaming)).then(stat => { if (!stat.isDirectory) { return this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } }).then(() => undefined); } diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 07258f7401..f8d5911316 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -51,16 +51,16 @@ export class OpenEditorsView extends ViewletPanel { static readonly ID = 'workbench.explorer.openEditorsView'; static NAME = nls.localize({ key: 'openEditors', comment: ['Open is an adjective'] }, "Open Editors"); - private dirtyCountElement: HTMLElement; + private dirtyCountElement!: HTMLElement; private listRefreshScheduler: RunOnceScheduler; private structuralRefreshDelay: number; - private list: WorkbenchList; - private listLabels: ResourceLabels; - private contributedContextMenu: IMenu; - private needsRefresh: boolean; - private resourceContext: ResourceContextKey; - private groupFocusedContext: IContextKey; - private dirtyEditorFocusedContext: IContextKey; + private list!: WorkbenchList; + private listLabels: ResourceLabels | undefined; + private contributedContextMenu!: IMenu; + private needsRefresh = false; + private resourceContext!: ResourceContextKey; + private groupFocusedContext!: IContextKey; + private dirtyEditorFocusedContext!: IContextKey; constructor( options: IViewletViewOptions, @@ -129,7 +129,7 @@ export class OpenEditorsView extends ViewletPanel { const index = this.getIndex(group, e.editor); switch (e.kind) { - case GroupChangeKind.GROUP_LABEL: { + case GroupChangeKind.GROUP_INDEX: { if (this.showGroups) { this.list.splice(index, 1, [group]); } @@ -466,9 +466,13 @@ interface IEditorGroupTemplateData { } class OpenEditorActionRunner extends ActionRunner { - public editor: OpenEditor; + public editor: OpenEditor | undefined; run(action: IAction, context?: any): Promise { + if (!this.editor) { + return Promise.resolve(); + } + return super.run(action, { groupId: this.editor.groupId, editorIndex: this.editor.editorIndex }); } } diff --git a/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts b/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts index 9927d0764e..98f4e3492a 100644 --- a/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts +++ b/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts @@ -19,7 +19,7 @@ import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/commo export class DirtyFilesTracker extends Disposable implements IWorkbenchContribution { private isDocumentedEdited: boolean; - private lastDirtyCount: number; + private lastKnownDirtyCount: number | undefined; private readonly badgeHandle = this._register(new MutableDisposable()); constructor( @@ -50,6 +50,10 @@ export class DirtyFilesTracker extends Disposable implements IWorkbenchContribut this.lifecycleService.onShutdown(this.dispose, this); } + private get hasDirtyCount(): boolean { + return typeof this.lastKnownDirtyCount === 'number' && this.lastKnownDirtyCount > 0; + } + private onUntitledDidChangeDirty(resource: URI): void { const gotDirty = this.untitledEditorService.isDirty(resource); @@ -57,7 +61,7 @@ export class DirtyFilesTracker extends Disposable implements IWorkbenchContribut this.updateDocumentEdited(); } - if (gotDirty || this.lastDirtyCount > 0) { + if (gotDirty || this.hasDirtyCount) { this.updateActivityBadge(); } } @@ -100,7 +104,7 @@ export class DirtyFilesTracker extends Disposable implements IWorkbenchContribut this.updateDocumentEdited(); } - if (this.lastDirtyCount > 0) { + if (this.hasDirtyCount) { this.updateActivityBadge(); } } @@ -118,15 +122,17 @@ export class DirtyFilesTracker extends Disposable implements IWorkbenchContribut this.updateDocumentEdited(); } - if (this.lastDirtyCount > 0) { + if (this.hasDirtyCount) { this.updateActivityBadge(); } } private updateActivityBadge(): void { const dirtyCount = this.textFileService.getDirty().length; - this.lastDirtyCount = dirtyCount; + this.lastKnownDirtyCount = dirtyCount; + this.badgeHandle.clear(); + if (dirtyCount > 0) { this.badgeHandle.value = this.activityService.showActivity(VIEWLET_ID, new NumberBadge(dirtyCount, num => num === 1 ? nls.localize('dirtyFile', "1 unsaved file") : nls.localize('dirtyFiles', "{0} unsaved files", dirtyCount)), 'explorer-viewlet-label'); } diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index 4842da934b..819c63858f 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -19,7 +19,7 @@ import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; export class ExplorerModel implements IDisposable { - private _roots: ExplorerItem[]; + private _roots!: ExplorerItem[]; private _listener: IDisposable; private _onDidChangeRoots = new Emitter(); @@ -75,7 +75,7 @@ export class ExplorerModel implements IDisposable { export class ExplorerItem { private _isDirectoryResolved: boolean; - public isError: boolean; + public isError = false; constructor( public resource: URI, @@ -367,4 +367,4 @@ export class NewExplorerItem extends ExplorerItem { constructor(parent: ExplorerItem, isDirectory: boolean) { super(URI.file(''), parent, isDirectory); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index bc458049ec..dcf8ee8fab 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -114,6 +114,7 @@ export interface IFilesConfiguration extends IFilesConfiguration, IWorkbenchEdit colors: boolean; badges: boolean; }; + incrementalNaming: 'simple' | 'smart'; }; editor: IEditorOptions; } diff --git a/src/vs/workbench/contrib/files/test/electron-browser/fileActions.test.ts b/src/vs/workbench/contrib/files/test/electron-browser/fileActions.test.ts index a46b9eef93..2aa086f577 100644 --- a/src/vs/workbench/contrib/files/test/electron-browser/fileActions.test.ts +++ b/src/vs/workbench/contrib/files/test/electron-browser/fileActions.test.ts @@ -6,132 +6,292 @@ import * as assert from 'assert'; import { incrementFileName } from 'vs/workbench/contrib/files/browser/fileActions'; -suite('Files - Increment file name', () => { +suite('Files - Increment file name simple', () => { test('Increment file name without any version', function () { const name = 'test.js'; - const result = incrementFileName(name, false); + const result = incrementFileName(name, false, 'simple'); assert.strictEqual(result, 'test copy.js'); }); test('Increment file name with suffix version', function () { const name = 'test copy.js'; - const result = incrementFileName(name, false); + const result = incrementFileName(name, false, 'simple'); assert.strictEqual(result, 'test copy 2.js'); }); test('Increment file name with suffix version with leading zeros', function () { const name = 'test copy 005.js'; - const result = incrementFileName(name, false); + const result = incrementFileName(name, false, 'simple'); assert.strictEqual(result, 'test copy 6.js'); }); test('Increment file name with suffix version, too big number', function () { const name = 'test copy 9007199254740992.js'; - const result = incrementFileName(name, false); + const result = incrementFileName(name, false, 'simple'); assert.strictEqual(result, 'test copy 9007199254740992 copy.js'); }); test('Increment file name with just version in name', function () { const name = 'copy.js'; - const result = incrementFileName(name, false); + const result = incrementFileName(name, false, 'simple'); assert.strictEqual(result, 'copy copy.js'); }); test('Increment file name with just version in name, v2', function () { const name = 'copy 2.js'; - const result = incrementFileName(name, false); + const result = incrementFileName(name, false, 'simple'); assert.strictEqual(result, 'copy 2 copy.js'); }); test('Increment file name without any extension or version', function () { const name = 'test'; - const result = incrementFileName(name, false); + const result = incrementFileName(name, false, 'simple'); assert.strictEqual(result, 'test copy'); }); test('Increment file name without any extension or version, trailing dot', function () { const name = 'test.'; - const result = incrementFileName(name, false); + const result = incrementFileName(name, false, 'simple'); assert.strictEqual(result, 'test copy.'); }); test('Increment file name without any extension or version, leading dot', function () { const name = '.test'; - const result = incrementFileName(name, false); + const result = incrementFileName(name, false, 'simple'); assert.strictEqual(result, '.test copy'); }); test('Increment file name without any extension or version, leading dot v2', function () { const name = '..test'; - const result = incrementFileName(name, false); + const result = incrementFileName(name, false, 'simple'); assert.strictEqual(result, '. copy.test'); }); test('Increment file name without any extension but with suffix version', function () { const name = 'test copy 5'; - const result = incrementFileName(name, false); + const result = incrementFileName(name, false, 'simple'); assert.strictEqual(result, 'test copy 6'); }); test('Increment folder name without any version', function () { const name = 'test'; - const result = incrementFileName(name, true); + const result = incrementFileName(name, true, 'simple'); assert.strictEqual(result, 'test copy'); }); test('Increment folder name with suffix version', function () { const name = 'test copy'; - const result = incrementFileName(name, true); + const result = incrementFileName(name, true, 'simple'); assert.strictEqual(result, 'test copy 2'); }); test('Increment folder name with suffix version, leading zeros', function () { const name = 'test copy 005'; - const result = incrementFileName(name, true); + const result = incrementFileName(name, true, 'simple'); assert.strictEqual(result, 'test copy 6'); }); test('Increment folder name with suffix version, too big number', function () { const name = 'test copy 9007199254740992'; - const result = incrementFileName(name, true); + const result = incrementFileName(name, true, 'simple'); assert.strictEqual(result, 'test copy 9007199254740992 copy'); }); test('Increment folder name with just version in name', function () { const name = 'copy'; - const result = incrementFileName(name, true); + const result = incrementFileName(name, true, 'simple'); assert.strictEqual(result, 'copy copy'); }); test('Increment folder name with just version in name, v2', function () { const name = 'copy 2'; - const result = incrementFileName(name, true); + const result = incrementFileName(name, true, 'simple'); assert.strictEqual(result, 'copy 2 copy'); }); test('Increment folder name "with extension" but without any version', function () { const name = 'test.js'; - const result = incrementFileName(name, true); + const result = incrementFileName(name, true, 'simple'); assert.strictEqual(result, 'test.js copy'); }); test('Increment folder name "with extension" and with suffix version', function () { const name = 'test.js copy 5'; - const result = incrementFileName(name, true); + const result = incrementFileName(name, true, 'simple'); assert.strictEqual(result, 'test.js copy 6'); }); test('Increment file/folder name with suffix version, special case 1', function () { const name = 'test copy 0'; - const result = incrementFileName(name, true); + const result = incrementFileName(name, true, 'simple'); assert.strictEqual(result, 'test copy'); }); test('Increment file/folder name with suffix version, special case 2', function () { const name = 'test copy 1'; - const result = incrementFileName(name, true); + const result = incrementFileName(name, true, 'simple'); assert.strictEqual(result, 'test copy 2'); }); }); + +suite('Files - Increment file name smart', () => { + + test('Increment file name without any version', function () { + const name = 'test.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, 'test.1.js'); + }); + + test('Increment folder name without any version', function () { + const name = 'test'; + const result = incrementFileName(name, true, 'smart'); + assert.strictEqual(result, 'test.1'); + }); + + test('Increment file name with suffix version', function () { + const name = 'test.1.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, 'test.2.js'); + }); + + test('Increment file name with suffix version with trailing zeros', function () { + const name = 'test.001.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, 'test.002.js'); + }); + + test('Increment file name with suffix version with trailing zeros, changing length', function () { + const name = 'test.009.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, 'test.010.js'); + }); + + test('Increment file name with suffix version with `-` as separator', function () { + const name = 'test-1.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, 'test-2.js'); + }); + + test('Increment file name with suffix version with `-` as separator, trailing zeros', function () { + const name = 'test-001.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, 'test-002.js'); + }); + + test('Increment file name with suffix version with `-` as separator, trailing zeros, changnig length', function () { + const name = 'test-099.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, 'test-100.js'); + }); + + test('Increment file name with suffix version with `_` as separator', function () { + const name = 'test_1.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, 'test_2.js'); + }); + + test('Increment folder name with suffix version', function () { + const name = 'test.1'; + const result = incrementFileName(name, true, 'smart'); + assert.strictEqual(result, 'test.2'); + }); + + test('Increment folder name with suffix version, trailing zeros', function () { + const name = 'test.001'; + const result = incrementFileName(name, true, 'smart'); + assert.strictEqual(result, 'test.002'); + }); + + test('Increment folder name with suffix version with `-` as separator', function () { + const name = 'test-1'; + const result = incrementFileName(name, true, 'smart'); + assert.strictEqual(result, 'test-2'); + }); + + test('Increment folder name with suffix version with `_` as separator', function () { + const name = 'test_1'; + const result = incrementFileName(name, true, 'smart'); + assert.strictEqual(result, 'test_2'); + }); + + test('Increment file name with suffix version, too big number', function () { + const name = 'test.9007199254740992.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, 'test.9007199254740992.1.js'); + }); + + test('Increment folder name with suffix version, too big number', function () { + const name = 'test.9007199254740992'; + const result = incrementFileName(name, true, 'smart'); + assert.strictEqual(result, 'test.9007199254740992.1'); + }); + + test('Increment file name with prefix version', function () { + const name = '1.test.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, '2.test.js'); + }); + + test('Increment file name with just version in name', function () { + const name = '1.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, '2.js'); + }); + + test('Increment file name with just version in name, too big number', function () { + const name = '9007199254740992.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, '9007199254740992.1.js'); + }); + + test('Increment file name with prefix version, trailing zeros', function () { + const name = '001.test.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, '002.test.js'); + }); + + test('Increment file name with prefix version with `-` as separator', function () { + const name = '1-test.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, '2-test.js'); + }); + + test('Increment file name with prefix version with `-` as separator', function () { + const name = '1_test.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, '2_test.js'); + }); + + test('Increment file name with prefix version, too big number', function () { + const name = '9007199254740992.test.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, '9007199254740992.test.1.js'); + }); + + test('Increment folder name with prefix version', function () { + const name = '1.test'; + const result = incrementFileName(name, true, 'smart'); + assert.strictEqual(result, '2.test'); + }); + + test('Increment folder name with prefix version, too big number', function () { + const name = '9007199254740992.test'; + const result = incrementFileName(name, true, 'smart'); + assert.strictEqual(result, '9007199254740992.test.1'); + }); + + test('Increment folder name with prefix version, trailing zeros', function () { + const name = '001.test'; + const result = incrementFileName(name, true, 'smart'); + assert.strictEqual(result, '002.test'); + }); + + test('Increment folder name with prefix version with `-` as separator', function () { + const name = '1-test'; + const result = incrementFileName(name, true, 'smart'); + assert.strictEqual(result, '2-test'); + }); + +}); diff --git a/src/vs/workbench/contrib/markers/browser/markersFileDecorations.ts b/src/vs/workbench/contrib/markers/browser/markersFileDecorations.ts index c35de708eb..436001217f 100644 --- a/src/vs/workbench/contrib/markers/browser/markersFileDecorations.ts +++ b/src/vs/workbench/contrib/markers/browser/markersFileDecorations.ts @@ -56,8 +56,8 @@ class MarkersDecorationsProvider implements IDecorationsProvider { class MarkersFileDecorations implements IWorkbenchContribution { private readonly _disposables: IDisposable[]; - private _provider: IDisposable; - private _enabled: boolean; + private _provider?: IDisposable; + private _enabled?: boolean; constructor( @IMarkerService private readonly _markerService: IMarkerService, diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 031cbd1368..6781a22e1b 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -334,9 +334,11 @@ class MarkerWidget extends Disposable { const sourceMatches = filterData && filterData.sourceMatches || []; source.set(marker.source, sourceMatches); - const code = new HighlightedLabel(dom.append(parent, dom.$('.marker-code')), false); - const codeMatches = filterData && filterData.codeMatches || []; - code.set(marker.code, codeMatches); + if (marker.code) { + const code = new HighlightedLabel(dom.append(parent, dom.$('.marker-code')), false); + const codeMatches = filterData && filterData.codeMatches || []; + code.set(marker.code, codeMatches); + } } const lnCol = dom.append(parent, dom.$('span.marker-line')); diff --git a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts index 257df5381a..0260b7d2b4 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts @@ -238,14 +238,14 @@ export class OutlinePanel extends ViewletPanel { private _editorDisposables = new DisposableStore(); private _outlineViewState = new OutlineViewState(); private _requestOracle?: RequestOracle; - private _domNode: HTMLElement; - private _message: HTMLDivElement; - private _inputContainer: HTMLDivElement; - private _progressBar: ProgressBar; - private _tree: WorkbenchDataTree; - private _treeDataSource: OutlineDataSource; - private _treeRenderer: OutlineElementRenderer; - private _treeComparator: OutlineItemComparator; + private _domNode!: HTMLElement; + private _message!: HTMLDivElement; + private _inputContainer!: HTMLDivElement; + private _progressBar!: ProgressBar; + private _tree!: WorkbenchDataTree; + private _treeDataSource!: OutlineDataSource; + private _treeRenderer!: OutlineElementRenderer; + private _treeComparator!: OutlineItemComparator; private _treeStates = new LRUCache(10); private _treeFakeUIEvent = new UIEvent('me'); diff --git a/src/vs/workbench/contrib/output/browser/outputActions.ts b/src/vs/workbench/contrib/output/browser/outputActions.ts index 902d2b5805..b63fe7262a 100644 --- a/src/vs/workbench/contrib/output/browser/outputActions.ts +++ b/src/vs/workbench/contrib/output/browser/outputActions.ts @@ -122,8 +122,8 @@ export class SwitchOutputActionViewItem extends SelectActionViewItem { private static readonly SEPARATOR = '─────────'; - private outputChannels: IOutputChannelDescriptor[]; - private logChannels: IOutputChannelDescriptor[]; + private outputChannels: IOutputChannelDescriptor[] = []; + private logChannels: IOutputChannelDescriptor[] = []; constructor( action: IAction, @@ -267,4 +267,4 @@ export class OpenOutputLogFileAction extends Action { return undefined; }); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/output/browser/outputPanel.ts b/src/vs/workbench/contrib/output/browser/outputPanel.ts index 26608c0a4e..b5f0def7c6 100644 --- a/src/vs/workbench/contrib/output/browser/outputPanel.ts +++ b/src/vs/workbench/contrib/output/browser/outputPanel.ts @@ -29,9 +29,9 @@ import { IWindowService } from 'vs/platform/windows/common/windows'; import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; export class OutputPanel extends AbstractTextResourceEditor { - private actions: IAction[]; + private actions: IAction[] | undefined; private scopedInstantiationService: IInstantiationService; - private _focus: boolean; + private _focus = false; constructor( @ITelemetryService telemetryService: ITelemetryService, @@ -155,7 +155,7 @@ export class OutputPanel extends AbstractTextResourceEditor { } const model = codeEditor.getModel(); - if (model) { + if (model && this.actions) { const newPositionLine = e.position.lineNumber; const lastLine = model.getLineCount(); const newLockState = lastLine !== newPositionLine; diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts index d54509e652..12bd616a4a 100644 --- a/src/vs/workbench/contrib/output/browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -71,7 +71,7 @@ export class OutputService extends Disposable implements IOutputService, ITextMo private readonly _onActiveOutputChannel = this._register(new Emitter()); readonly onActiveOutputChannel: Event = this._onActiveOutputChannel.event; - private _outputPanel: OutputPanel; + private _outputPanel: OutputPanel | undefined; constructor( @IStorageService private readonly storageService: IStorageService, @@ -224,7 +224,7 @@ export class OutputService extends Disposable implements IOutputService, ITextMo CONTEXT_ACTIVE_LOG_OUTPUT.bindTo(this.contextKeyService).set(!!channel.outputChannelDescriptor.file && channel.outputChannelDescriptor.log); return this._outputPanel.setInput(this.createInput(channel), EditorOptions.create({ preserveFocus }), CancellationToken.None) .then(() => { - if (!preserveFocus) { + if (!preserveFocus && this._outputPanel) { this._outputPanel.focus(); } }); diff --git a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts index 5c22cd986b..f1c18dab7a 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts @@ -152,8 +152,8 @@ class PerfModelContentProvider implements ITextModelContentProvider { table.push(['nls:start => nls:end', metrics.timers.ellapsedNlsGeneration, '[main]', `initial startup: ${metrics.initialStartup}`]); table.push(['require(main.bundle.js)', metrics.initialStartup ? perf.getDuration('willLoadMainBundle', 'didLoadMainBundle') : undefined, '[main]', `initial startup: ${metrics.initialStartup}`]); table.push(['app.isReady => window.loadUrl()', metrics.timers.ellapsedWindowLoad, '[main]', `initial startup: ${metrics.initialStartup}`]); - table.push(['window.loadUrl() => begin to require(workbench.main.js)', metrics.timers.ellapsedWindowLoadToRequire, '[main->renderer]', StartupKindToString(metrics.windowKind)]); - table.push(['require(workbench.main.js)', metrics.timers.ellapsedRequire, '[renderer]', `cached data: ${(metrics.didUseCachedData ? 'YES' : 'NO')}${stats ? `, node_modules took ${stats.nodeRequireTotal}ms` : ''}`]); + table.push(['window.loadUrl() => begin to require(workbench.desktop.main.js)', metrics.timers.ellapsedWindowLoadToRequire, '[main->renderer]', StartupKindToString(metrics.windowKind)]); + table.push(['require(workbench.desktop.main.js)', metrics.timers.ellapsedRequire, '[renderer]', `cached data: ${(metrics.didUseCachedData ? 'YES' : 'NO')}${stats ? `, node_modules took ${stats.nodeRequireTotal}ms` : ''}`]); table.push(['require & init workspace storage', metrics.timers.ellapsedWorkspaceStorageInit, '[renderer]', undefined]); table.push(['init workspace service', metrics.timers.ellapsedWorkspaceServiceInit, '[renderer]', undefined]); table.push(['register extensions & spawn extension host', metrics.timers.ellapsedExtensions, '[renderer]', undefined]); @@ -262,11 +262,11 @@ class PerfModelContentProvider implements ITextModelContentProvider { } abstract class LoaderStats { - readonly amdLoad: (string | number)[][]; - readonly amdInvoke: (string | number)[][]; - readonly nodeRequire: (string | number)[][]; - readonly nodeEval: (string | number)[][]; - readonly nodeRequireTotal: number; + abstract get amdLoad(): (string | number)[][]; + abstract get amdInvoke(): (string | number)[][]; + abstract get nodeRequire(): (string | number)[][]; + abstract get nodeEval(): (string | number)[][]; + abstract get nodeRequireTotal(): number; static get(): LoaderStats { diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index 91e0cf8f30..217e75d4cb 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -143,7 +143,7 @@ max-width: 952px; /* 1000 - 24*2 padding */ margin-left: -476px; - z-index: 1000; + z-index: 11; } .settings-editor > .settings-body .settings-tree-container .setting-toolbar-container { @@ -182,7 +182,7 @@ .settings-editor > .settings-body .settings-toc-container { width: 100%; pointer-events: none; - z-index: 100; + z-index: 10; position: absolute; } @@ -511,4 +511,4 @@ .settings-editor.search-mode > .settings-body .settings-toc-container .monaco-list-row .settings-toc-count { display: block; -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css b/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css index b0c2321445..c3d773a0a7 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css @@ -29,7 +29,7 @@ .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-value, .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-sibling { display: inline-block; - line-height: 22px; + line-height: 24px; } /* Use monospace to display glob patterns in exclude widget */ @@ -49,12 +49,11 @@ display: none; position: absolute; right: 0px; - margin-top: 1px; } .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-row { position: relative; - max-height: 22px; + max-height: 24px; } .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-row:focus { @@ -68,7 +67,7 @@ .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-row .monaco-action-bar .action-label { width: 16px; - height: 16px; + height: 20px; padding: 2px; margin-right: 2px; } @@ -103,6 +102,7 @@ } .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .monaco-text-button.setting-list-addButton { + margin-top: 4px; margin-right: 10px; } @@ -112,7 +112,7 @@ .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-valueInput, .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-siblingInput { - height: 22px; + height: 24px; max-width: 320px; flex: 1; margin-right: 10px; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 9cfcb7f0cc..df671f4cab 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -289,6 +289,11 @@ export class SettingsEditor2 extends BaseEditor { layout(dimension: DOM.Dimension): void { this.dimension = dimension; + + if (!this.isVisible()) { + return; + } + this.layoutTrees(dimension); const innerWidth = Math.min(1000, dimension.width) - 24 * 2; // 24px padding on left and right; @@ -978,14 +983,9 @@ export class SettingsEditor2 extends BaseEditor { // If a single setting is being refreshed, it's ok to refresh now if that is not the focused setting if (key) { const focusedKey = focusedSetting.getAttribute(AbstractSettingRenderer.SETTING_KEY_ATTR); - /** - * Update `list`s live if focused item is whole list or list item, - * as they have a separate "submit edit" step built in before this - */ - if ( - focusedKey === key && - !DOM.hasClass(focusedSetting, 'setting-item-list') && - !DOM.hasClass(focusedSetting, 'setting-item-contents') + if (focusedKey === key && + // update `list`s live, as they have a separate "submit edit" step built in before this + (focusedSetting.parentElement && !DOM.hasClass(focusedSetting.parentElement, 'setting-item-list')) ) { this.updateModifiedLabelForKey(key); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index fa48ce55e1..fa1afa47b1 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -100,6 +100,11 @@ export const tocData: ITOCEntry = { id: 'workbench/zenmode', label: localize('zenMode', "Zen Mode"), settings: ['zenmode.*'] + }, + { + id: 'workbench/screencastmode', + label: localize('screencastMode', "Screencast Mode"), + settings: ['screencastMode.*'] } ] }, diff --git a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts index 3d2a68bd8b..4a7d477253 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts @@ -16,7 +16,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import 'vs/css!./media/settingsWidgets'; import { localize } from 'vs/nls'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { foreground, inputBackground, inputBorder, inputForeground, listActiveSelectionBackground, listActiveSelectionForeground, listHoverBackground, listHoverForeground, listInactiveSelectionBackground, listInactiveSelectionForeground, registerColor, selectBackground, selectBorder, selectForeground, textLinkForeground, textPreformatForeground, editorWidgetBorder, textLinkActiveForeground } from 'vs/platform/theme/common/colorRegistry'; +import { foreground, inputBackground, inputBorder, inputForeground, listActiveSelectionBackground, listActiveSelectionForeground, listHoverBackground, listHoverForeground, listInactiveSelectionBackground, listInactiveSelectionForeground, registerColor, selectBackground, selectBorder, selectForeground, textLinkForeground, textPreformatForeground, editorWidgetBorder, textLinkActiveForeground, simpleCheckboxBackground, simpleCheckboxForeground, simpleCheckboxBorder } from 'vs/platform/theme/common/colorRegistry'; import { attachButtonStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { disposableTimeout } from 'vs/base/common/async'; @@ -37,9 +37,9 @@ export const settingsSelectBorder = registerColor('settings.dropdownBorder', { d export const settingsSelectListBorder = registerColor('settings.dropdownListBorder', { dark: editorWidgetBorder, light: editorWidgetBorder, hc: editorWidgetBorder }, localize('settingsDropdownListBorder', "(For settings editor preview) Settings editor dropdown list border. This surrounds the options and separates the options from the description.")); // Bool control colors -export const settingsCheckboxBackground = registerColor('settings.checkboxBackground', { dark: selectBackground, light: selectBackground, hc: selectBackground }, localize('settingsCheckboxBackground', "(For settings editor preview) Settings editor checkbox background.")); -export const settingsCheckboxForeground = registerColor('settings.checkboxForeground', { dark: selectForeground, light: selectForeground, hc: selectForeground }, localize('settingsCheckboxForeground', "(For settings editor preview) Settings editor checkbox foreground.")); -export const settingsCheckboxBorder = registerColor('settings.checkboxBorder', { dark: selectBorder, light: selectBorder, hc: selectBorder }, localize('settingsCheckboxBorder', "(For settings editor preview) Settings editor checkbox border.")); +export const settingsCheckboxBackground = registerColor('settings.checkboxBackground', { dark: simpleCheckboxBackground, light: simpleCheckboxBackground, hc: simpleCheckboxBackground }, localize('settingsCheckboxBackground', "(For settings editor preview) Settings editor checkbox background.")); +export const settingsCheckboxForeground = registerColor('settings.checkboxForeground', { dark: simpleCheckboxForeground, light: simpleCheckboxForeground, hc: simpleCheckboxForeground }, localize('settingsCheckboxForeground', "(For settings editor preview) Settings editor checkbox foreground.")); +export const settingsCheckboxBorder = registerColor('settings.checkboxBorder', { dark: simpleCheckboxBorder, light: simpleCheckboxBorder, hc: simpleCheckboxBorder }, localize('settingsCheckboxBorder', "(For settings editor preview) Settings editor checkbox border.")); // Text control colors export const settingsTextInputBackground = registerColor('settings.textInputBackground', { dark: inputBackground, light: inputBackground, hc: inputBackground }, localize('textInputBoxBackground', "(For settings editor preview) Settings editor text input box background.")); @@ -346,7 +346,7 @@ export class ListSettingWidget extends Disposable { .map((item, i) => this.renderItem(item, i, focused)) .forEach(itemElement => this.listElement.appendChild(itemElement)); - const listHeight = 22 * this.model.items.length; + const listHeight = 24 * this.model.items.length; this.listElement.style.height = listHeight + 'px'; } diff --git a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts index e04f0aa3fa..0a95a958a3 100644 --- a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts @@ -366,7 +366,7 @@ export class GotoSymbolHandler extends QuickOpenHandler { static readonly ID = 'workbench.picker.filesymbols'; private rangeHighlightDecorationId?: IEditorLineDecoration; - private lastKnownEditorViewState: IEditorViewState | null; + private lastKnownEditorViewState: IEditorViewState | null = null; private cachedOutlineRequest?: Promise; private pendingOutlineRequest?: CancellationTokenSource; diff --git a/src/vs/workbench/contrib/relauncher/electron-browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/common/relauncher.contribution.ts similarity index 76% rename from src/vs/workbench/contrib/relauncher/electron-browser/relauncher.contribution.ts rename to src/vs/workbench/contrib/relauncher/common/relauncher.contribution.ts index 23205cd1d1..8ae8f52ebe 100644 --- a/src/vs/workbench/contrib/relauncher/electron-browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/common/relauncher.contribution.ts @@ -15,7 +15,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { RunOnceScheduler } from 'vs/base/common/async'; import { URI } from 'vs/base/common/uri'; import { isEqual } from 'vs/base/common/resources'; -import { isMacintosh } from 'vs/base/common/platform'; +import { isMacintosh, isNative } from 'vs/base/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -23,7 +23,6 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ interface IConfiguration extends IWindowsConfiguration { update: { mode: string; }; telemetry: { enableCrashReporter: boolean }; - keyboard: { touchbar: { enabled: boolean } }; workbench: { list: { horizontalScrolling: boolean }, useExperimentalGridLayout: boolean }; debug: { console: { wordWrap: boolean } }; } @@ -36,7 +35,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo private clickThroughInactive: boolean; private updateMode: string; private enableCrashReporter: boolean; - private touchbarEnabled: boolean; private treeHorizontalScrolling: boolean; private useGridLayout: boolean; private debugConsoleWordWrap: boolean; @@ -57,48 +55,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo private onConfigurationChange(config: IConfiguration, notify: boolean): void { let changed = false; - // Titlebar style - if (config.window && config.window.titleBarStyle !== this.titleBarStyle && (config.window.titleBarStyle === 'native' || config.window.titleBarStyle === 'custom')) { - this.titleBarStyle = config.window.titleBarStyle; - changed = true; - } - - // macOS: Native tabs - if (isMacintosh && config.window && typeof config.window.nativeTabs === 'boolean' && config.window.nativeTabs !== this.nativeTabs) { - this.nativeTabs = config.window.nativeTabs; - changed = true; - } - - // macOS: Native fullscreen - if (isMacintosh && config.window && typeof config.window.nativeFullScreen === 'boolean' && config.window.nativeFullScreen !== this.nativeFullScreen) { - this.nativeFullScreen = config.window.nativeFullScreen; - changed = true; - } - - // macOS: Click through (accept first mouse) - if (isMacintosh && config.window && typeof config.window.clickThroughInactive === 'boolean' && config.window.clickThroughInactive !== this.clickThroughInactive) { - this.clickThroughInactive = config.window.clickThroughInactive; - changed = true; - } - - // Update channel - if (config.update && typeof config.update.mode === 'string' && config.update.mode !== this.updateMode) { - this.updateMode = config.update.mode; - changed = true; - } - - // Crash reporter - if (config.telemetry && typeof config.telemetry.enableCrashReporter === 'boolean' && config.telemetry.enableCrashReporter !== this.enableCrashReporter) { - this.enableCrashReporter = config.telemetry.enableCrashReporter; - changed = true; - } - - // macOS: Touchbar config - if (isMacintosh && config.keyboard && config.keyboard.touchbar && typeof config.keyboard.touchbar.enabled === 'boolean' && config.keyboard.touchbar.enabled !== this.touchbarEnabled) { - this.touchbarEnabled = config.keyboard.touchbar.enabled; - changed = true; - } - // Tree horizontal scrolling support if (config.workbench && config.workbench.list && typeof config.workbench.list.horizontalScrolling === 'boolean' && config.workbench.list.horizontalScrolling !== this.treeHorizontalScrolling) { this.treeHorizontalScrolling = config.workbench.list.horizontalScrolling; @@ -117,12 +73,57 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo changed = true; } + if (isNative) { + + // Titlebar style + if (config.window && config.window.titleBarStyle !== this.titleBarStyle && (config.window.titleBarStyle === 'native' || config.window.titleBarStyle === 'custom')) { + this.titleBarStyle = config.window.titleBarStyle; + changed = true; + } + + // macOS: Native tabs + if (isMacintosh && config.window && typeof config.window.nativeTabs === 'boolean' && config.window.nativeTabs !== this.nativeTabs) { + this.nativeTabs = config.window.nativeTabs; + changed = true; + } + + // macOS: Native fullscreen + if (isMacintosh && config.window && typeof config.window.nativeFullScreen === 'boolean' && config.window.nativeFullScreen !== this.nativeFullScreen) { + this.nativeFullScreen = config.window.nativeFullScreen; + changed = true; + } + + // macOS: Click through (accept first mouse) + if (isMacintosh && config.window && typeof config.window.clickThroughInactive === 'boolean' && config.window.clickThroughInactive !== this.clickThroughInactive) { + this.clickThroughInactive = config.window.clickThroughInactive; + changed = true; + } + + // Update channel + if (config.update && typeof config.update.mode === 'string' && config.update.mode !== this.updateMode) { + this.updateMode = config.update.mode; + changed = true; + } + + // Crash reporter + if (config.telemetry && typeof config.telemetry.enableCrashReporter === 'boolean' && config.telemetry.enableCrashReporter !== this.enableCrashReporter) { + this.enableCrashReporter = config.telemetry.enableCrashReporter; + changed = true; + } + } + // Notify only when changed and we are the focused window (avoids notification spam across windows) if (notify && changed) { this.doConfirm( - localize('relaunchSettingMessage', "A setting has changed that requires a restart to take effect."), - localize('relaunchSettingDetail', "Press the restart button to restart {0} and enable the setting.", this.envService.appNameLong), - localize('restart', "&&Restart"), + isNative ? + localize('relaunchSettingMessage', "A setting has changed that requires a restart to take effect.") : + localize('relaunchSettingMessageWeb', "A setting has changed that requires a reload to take effect."), + isNative ? + localize('relaunchSettingDetail', "Press the restart button to restart {0} and enable the setting.", this.envService.appNameLong) : + localize('relaunchSettingDetailWeb', "Press the reload button to reload {0} and enable the setting.", this.envService.appNameLong), + isNative ? + localize('restart', "&&Restart") : + localize('restartWeb', "&&Reload"), () => this.windowsService.relaunch(Object.create(null)) ); } @@ -167,8 +168,9 @@ export class WorkspaceChangeExtHostRelauncher extends Disposable implements IWor if (!!environmentService.extensionTestsLocationURI) { return; // no restart when in tests: see https://github.com/Microsoft/vscode/issues/66936 } + if (environmentService.configuration.remoteAuthority) { - windowSevice.reloadWindow(); // TODO aeschli, workaround + windowSevice.reloadWindow(); // TODO@aeschli, workaround } else { extensionService.restartExtensionHost(); } diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index f5e16ee5d9..489bee2678 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -85,7 +85,7 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc group: '6_close', command: { id: CLOSE_REMOTE_COMMAND_ID, - title: nls.localize({ key: 'miCloseRemote', comment: ['&& denotes a mnemonic'] }, "C&&lose Remote Connection") + title: nls.localize({ key: 'miCloseRemote', comment: ['&& denotes a mnemonic'] }, "Close Re&&mote Connection") }, order: 3.5 }); diff --git a/src/vs/workbench/contrib/resources/browser/resourceServiceWorker.ts b/src/vs/workbench/contrib/resources/browser/resourceServiceWorker.ts index f1b1abe8e1..316020fcb0 100644 --- a/src/vs/workbench/contrib/resources/browser/resourceServiceWorker.ts +++ b/src/vs/workbench/contrib/resources/browser/resourceServiceWorker.ts @@ -4,8 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { generateUuid } from 'vs/base/common/uuid'; -import { getMediaMime } from 'vs/base/common/mime'; //https://stackoverflow.com/questions/56356655/structuring-a-typescript-project-with-workers/56374158#56374158 declare var self: ServiceWorkerGlobalScope; @@ -35,8 +33,8 @@ self.addEventListener('activate', event => { //#region --- fetching/caching -const _cacheName = 'vscode-resources'; -const _resourcePrefix = '/vscode-resources/fetch'; +const _cacheName = 'vscode-extension-resources'; +const _resourcePrefix = '/vscode-remote'; const _pendingFetch = new Map(); self.addEventListener('message', event => { @@ -71,38 +69,18 @@ async function respondWithDefault(event: FetchEvent): Promise { } async function respondWithResource(event: FetchEvent, uri: URI): Promise { - const cacheKey = event.request.url.replace('&r=1', ''); - const cachedValue = await caches.open(_cacheName).then(cache => cache.match(cacheKey)); + + const cachedValue = await caches.open(_cacheName).then(cache => cache.match(event.request)); if (cachedValue) { return cachedValue; } - return new Promise(resolve => { + const response: Response = await event.preloadResponse || await fetch(event.request); + if (response.headers.get('X-VSCode-Extension') === 'true') { + await caches.open(_cacheName).then(cache => cache.put(event.request, response.clone())); + } - const token = generateUuid(); - const [first] = uri.query.split('&'); - const components = JSON.parse(first.substr(2)); - - _pendingFetch.set(token, async (data: ArrayBuffer, isExtensionResource: boolean) => { - - const res = new Response(data, { - status: 200, - headers: { 'Content-Type': getMediaMime(components.path) || 'text/plain' } - }); - - if (isExtensionResource) { - // only cache extension resources but not other - // resources, esp not workspace resources - await caches.open(_cacheName).then(cache => cache.put(cacheKey, res.clone())); - } - - return resolve(res); - }); - - self.clients.get(event.clientId).then(client => { - client.postMessage({ uri: components, token }); - }); - }); + return response; } //#endregion diff --git a/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerClient.ts b/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerClient.ts index 19220cb30a..f45cf904e4 100644 --- a/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerClient.ts +++ b/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerClient.ts @@ -3,145 +3,41 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IFileService } from 'vs/platform/files/common/files'; -import { URI } from 'vs/base/common/uri'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { isEqualOrParent } from 'vs/base/common/resources'; import { ILogService } from 'vs/platform/log/common/log'; - -// load and start service worker as soon as this file -// is being loaded and later, when services are ready, -// claim this service worker so that messages can be -// replied to -const _serviceWorker = new class ServiceWorkerStarter { - - private static _url = require.toUrl('./resourceServiceWorkerMain.js'); - - private _beforeReadyEvents: ExtendableMessageEvent[] = []; - private _messageHandler?: (event: ExtendableMessageEvent) => void; - - constructor() { - navigator.serviceWorker.register(ServiceWorkerStarter._url, { scope: '/' }).then(reg => { - // console.debug('SW#reg', reg); - return reg.update(); - // }).then(() => { - // // console.debug('SW#updated', reg); - // return navigator.serviceWorker.ready; - }).then(() => { - console.info('SW#ready'); - }).catch(err => { - console.error('SW#init', err); - }); - - const handleMessage = (event: ExtendableMessageEvent) => { - if (!this._messageHandler) { - this._beforeReadyEvents.push(event); - console.debug('SW#buffered', event.data); - } else { - this._messageHandler(event); - } - }; - - navigator.serviceWorker.addEventListener('message', e => handleMessage(e as ExtendableMessageEvent)); - } - - dispose(): void { - // when to dispose? - } - - claim(handler: (event: ExtendableMessageEvent) => void): void { - this._messageHandler = handler; - this._beforeReadyEvents.forEach(this._messageHandler); - } -}; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; class ResourceServiceWorker { + private static _url = require.toUrl('./resourceServiceWorkerMain.js'); + + private readonly _disposables = new DisposableStore(); + constructor( - @IFileService private readonly _fileService: IFileService, - @IExtensionService private readonly _extensionService: IExtensionService, @ILogService private readonly _logService: ILogService, ) { - this._updateEarlyResourceUris(); - _serviceWorker.claim(e => this._handleMessage(e)); + navigator.serviceWorker.register(ResourceServiceWorker._url, { scope: '/' }).then(reg => { + this._logService.trace('SW#reg', reg); + return reg.update(); + }).then(() => { + this._logService.info('SW#ready'); + }).catch(err => { + this._logService.error('SW#init', err); + }); + + const handler = (e: ExtendableMessageEvent) => this._handleMessage(e); + navigator.serviceWorker.addEventListener('message', handler); + this._disposables.add(toDisposable(() => navigator.serviceWorker.removeEventListener('message', handler))); + } + + dispose(): void { + this._disposables.dispose(); } private _handleMessage(event: ExtendableMessageEvent): void { - this._logService.trace('SW - fetch', event.data.uri); - - const uri = URI.revive(event.data.uri); - Promise.all([ - this._fileService.readFile(uri), - this._isExtensionResource(uri) - ]).then(([file, isExtensionResource]) => { - if (!event.source) { - return; - } - event.source.postMessage({ - token: event.data.token, - data: file.value.buffer.buffer, - isExtensionResource - }, [file.value.buffer.buffer]); - }); - } - - private async _isExtensionResource(uri: URI): Promise { - for (const ext of await this._extensionService.getExtensions()) { - if (isEqualOrParent(uri, ext.extensionLocation)) { - return true; - } - } - return false; - } - - private _updateEarlyResourceUris(): void { - - let updateCount = 0; - - // find style-tags - const styleElements = document.querySelectorAll('style'); - for (let i = 0; i < styleElements.length; i++) { - const el = styleElements.item(i); - if (!el.sheet) { - continue; - } - const rules = (el.sheet).rules; - for (let j = 0; j < rules.length; j++) { - const rule = rules[j]; - const newCssText = this._updateResourceUris(rule.cssText); - if (newCssText) { - (el.sheet).deleteRule(j); - (el.sheet).insertRule(newCssText, j); - updateCount += 1; - } - } - } - - // find any tag using remote uris - const htmlElements = document.querySelectorAll('[style*="/vscode-resources/fetch"]'); - for (let i = 0; i < htmlElements.length; i++) { - const el = htmlElements.item(i); - const newCssText = this._updateResourceUris(el.style.cssText); - if (newCssText) { - el.style.cssText = newCssText; - updateCount += 1; - } - } - - this._logService.trace('SW - count of changed, early dom element: ', updateCount); - } - - private _updateResourceUris(cssText: string): string | undefined { - let changed = false; - let newCssText = cssText.replace(/url\((["'])?(.+?\/vscode-resources\/fetch\?.+?)\1\)/g, (_match, g1, g2, _offset, _input) => { - changed = true; - return `url(${g1 || ''}${g2}&r=1${g1 || ''})`; - }); - - return changed ? newCssText : undefined; + this._logService.trace('SW', event.data); } } diff --git a/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerMain.ts b/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerMain.ts index 87eaf7829c..9c008dbad7 100644 --- a/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerMain.ts +++ b/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerMain.ts @@ -10,7 +10,7 @@ // statement. // trigger service worker updates -const _tag = 'a6f9835e-c10e-4299-ab39-b8e29547c20a'; +const _tag = '52278406-3ca9-48af-a8fb-8495add5bb4e'; // loader world const baseUrl = '../../../../../'; diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 8e290806f0..c0c0f5b97b 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -234,7 +234,7 @@ class DirtyDiffWidget extends PeekViewWidget { const changeTypeColor = getChangeTypeColor(this.themeService.getTheme(), changeType); this.style({ frameColor: changeTypeColor, arrowColor: changeTypeColor }); - this._actionbarWidget.context = [this.model.modified!.uri, this.model.changes, index]; + this._actionbarWidget!.context = [this.model.modified!.uri, this.model.changes, index]; this.show(position, height); this.editor.focus(); } @@ -255,11 +255,11 @@ class DirtyDiffWidget extends PeekViewWidget { this._disposables.add(previous); this._disposables.add(next); - this._actionbarWidget.push([previous, next], { label: false, icon: true }); + this._actionbarWidget!.push([previous, next], { label: false, icon: true }); const actions: IAction[] = []; this._disposables.add(createAndFillInActionBarActions(this.menu, { shouldForwardArgs: true }, actions)); - this._actionbarWidget.push(actions, { label: false, icon: true }); + this._actionbarWidget!.push(actions, { label: false, icon: true }); } protected _getActionBarOptions(): IActionBarOptions { diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index fb64a95fa0..54aaf2ab62 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -13,10 +13,10 @@ import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } fro import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { StatusUpdater, StatusBarController } from './scmActivity'; +import { SCMStatusController } from './scmActivity'; import { SCMViewlet } from 'vs/workbench/contrib/scm/browser/scmViewlet'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -48,10 +48,7 @@ Registry.as(ViewletExtensions.Viewlets).registerViewlet(new Vie )); Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(StatusUpdater, LifecyclePhase.Restored); - -Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(StatusBarController, LifecyclePhase.Restored); + .registerWorkbenchContribution(SCMStatusController, LifecyclePhase.Restored); // Register Action to Open Viewlet Registry.as(WorkbenchActionExtensions.WorkbenchActions).registerWorkbenchAction( @@ -70,10 +67,11 @@ Registry.as(ConfigurationExtensions.Configuration).regis order: 5, title: localize('scmConfigurationTitle', "SCM"), type: 'object', + scope: ConfigurationScope.RESOURCE, properties: { 'scm.alwaysShowProviders': { type: 'boolean', - description: localize('alwaysShowProviders', "Controls whether to always show the Source Control Provider section."), + description: localize('alwaysShowProviders', "Controls whether to show the Source Control Provider section even when there's only one Provider registered."), default: false }, 'scm.providers.visible': { @@ -97,6 +95,17 @@ Registry.as(ConfigurationExtensions.Configuration).regis type: 'boolean', description: localize('alwaysShowActions', "Controls whether inline actions are always visible in the Source Control view."), default: false + }, + 'scm.countBadge': { + type: 'string', + enum: ['all', 'focused', 'off'], + enumDescriptions: [ + localize('scm.countBadge.all', "Show the sum of all Source Control Providers count badges."), + localize('scm.countBadge.focused', "Show the count badge of the focused Source Control Provider."), + localize('scm.countBadge.off', "Disable the Source Control count badge.") + ], + description: localize('scm.countBadge', "Controls the Source Control count badge."), + default: 'all' } } }); diff --git a/src/vs/workbench/contrib/scm/browser/scmActivity.ts b/src/vs/workbench/contrib/scm/browser/scmActivity.ts index 5cdee688aa..ae7c95c4b8 100644 --- a/src/vs/workbench/contrib/scm/browser/scmActivity.ts +++ b/src/vs/workbench/contrib/scm/browser/scmActivity.ts @@ -14,92 +14,45 @@ import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/c import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { commonPrefixLength } from 'vs/base/common/strings'; -import { ILogService } from 'vs/platform/log/common/log'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -export class StatusUpdater implements IWorkbenchContribution { - - private readonly badgeDisposable = new MutableDisposable(); - private disposables: IDisposable[] = []; - - constructor( - @ISCMService private readonly scmService: ISCMService, - @IActivityService private readonly activityService: IActivityService, - @ILogService private readonly logService: ILogService - ) { - for (const repository of this.scmService.repositories) { - this.onDidAddRepository(repository); - } - - this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); - this.render(); - } - - private onDidAddRepository(repository: ISCMRepository): void { - const provider = repository.provider; - const onDidChange = Event.any(provider.onDidChange, provider.onDidChangeResources); - const changeDisposable = onDidChange(() => this.render()); - - const onDidRemove = Event.filter(this.scmService.onDidRemoveRepository, e => e === repository); - const removeDisposable = onDidRemove(() => { - disposable.dispose(); - this.disposables = this.disposables.filter(d => d !== removeDisposable); - this.render(); - }); - - const disposable = combinedDisposable(changeDisposable, removeDisposable); - this.disposables.push(disposable); - } - - private render(): void { - this.badgeDisposable.clear(); - - const count = this.scmService.repositories.reduce((r, repository) => { - if (typeof repository.provider.count === 'number') { - return r + repository.provider.count; - } else { - return r + repository.provider.groups.elements.reduce((r, g) => r + g.elements.length, 0); - } - }, 0); - - // TODO@joao: remove - this.logService.trace('SCM#StatusUpdater.render', count); - - if (count > 0) { - const badge = new NumberBadge(count, num => localize('scmPendingChangesBadge', '{0} pending changes', num)); - this.badgeDisposable.value = this.activityService.showActivity(VIEWLET_ID, badge, 'scm-viewlet-label'); - } else { - this.badgeDisposable.clear(); - } - } - - dispose(): void { - this.badgeDisposable.dispose(); - this.disposables = dispose(this.disposables); +function getCount(repository: ISCMRepository): number { + if (typeof repository.provider.count === 'number') { + return repository.provider.count; + } else { + return repository.provider.groups.elements.reduce((r, g) => r + g.elements.length, 0); } } -export class StatusBarController implements IWorkbenchContribution { +export class SCMStatusController implements IWorkbenchContribution { private statusBarDisposable: IDisposable = Disposable.None; private focusDisposable: IDisposable = Disposable.None; private focusedRepository: ISCMRepository | undefined = undefined; private focusedProviderContextKey: IContextKey; + private readonly badgeDisposable = new MutableDisposable(); private disposables: IDisposable[] = []; constructor( @ISCMService private readonly scmService: ISCMService, @IStatusbarService private readonly statusbarService: IStatusbarService, - @IContextKeyService contextKeyService: IContextKeyService, - @IEditorService private readonly editorService: IEditorService + @IContextKeyService readonly contextKeyService: IContextKeyService, + @IActivityService private readonly activityService: IActivityService, + @IEditorService private readonly editorService: IEditorService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { this.focusedProviderContextKey = contextKeyService.createKey('scmProvider', undefined); this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); + const onDidChangeSCMCountBadge = Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.countBadge')); + onDidChangeSCMCountBadge(this.renderActivityCount, this, this.disposables); + for (const repository of this.scmService.repositories) { this.onDidAddRepository(repository); } editorService.onDidActiveEditorChange(this.onDidActiveEditorChange, this, this.disposables); + this.renderActivityCount(); } private onDidActiveEditorChange(): void { @@ -138,7 +91,11 @@ export class StatusBarController implements IWorkbenchContribution { } private onDidAddRepository(repository: ISCMRepository): void { - const changeDisposable = repository.onDidFocus(() => this.onDidFocusRepository(repository)); + const focusDisposable = repository.onDidFocus(() => this.onDidFocusRepository(repository)); + + const onDidChange = Event.any(repository.provider.onDidChange, repository.provider.onDidChangeResources); + const changeDisposable = onDidChange(() => this.renderActivityCount()); + const onDidRemove = Event.filter(this.scmService.onDidRemoveRepository, e => e === repository); const removeDisposable = onDidRemove(() => { disposable.dispose(); @@ -149,9 +106,11 @@ export class StatusBarController implements IWorkbenchContribution { } else if (this.focusedRepository === repository) { this.scmService.repositories[0].focus(); } + + this.renderActivityCount(); }); - const disposable = combinedDisposable(changeDisposable, removeDisposable); + const disposable = combinedDisposable(focusDisposable, changeDisposable, removeDisposable); this.disposables.push(disposable); if (!this.focusedRepository) { @@ -169,13 +128,14 @@ export class StatusBarController implements IWorkbenchContribution { this.focusDisposable.dispose(); if (repository && repository.provider.onDidChangeStatusBarCommands) { - this.focusDisposable = repository.provider.onDidChangeStatusBarCommands(() => this.render(repository)); + this.focusDisposable = repository.provider.onDidChangeStatusBarCommands(() => this.renderStatusBar(repository)); } - this.render(repository); + this.renderStatusBar(repository); + this.renderActivityCount(); } - private render(repository: ISCMRepository | undefined): void { + private renderStatusBar(repository: ISCMRepository | undefined): void { this.statusBarDisposable.dispose(); if (!repository) { @@ -200,9 +160,31 @@ export class StatusBarController implements IWorkbenchContribution { this.statusBarDisposable = disposables; } + private renderActivityCount(): void { + this.badgeDisposable.clear(); + + const countBadgeType = this.configurationService.getValue<'all' | 'focused' | 'off'>('scm.countBadge'); + + let count = 0; + + if (countBadgeType === 'all') { + count = this.scmService.repositories.reduce((r, repository) => r + getCount(repository), 0); + } else if (countBadgeType === 'focused' && this.focusedRepository) { + count = getCount(this.focusedRepository); + } + + if (count > 0) { + const badge = new NumberBadge(count, num => localize('scmPendingChangesBadge', '{0} pending changes', num)); + this.badgeDisposable.value = this.activityService.showActivity(VIEWLET_ID, badge, 'scm-viewlet-label'); + } else { + this.badgeDisposable.clear(); + } + } + dispose(): void { this.focusDisposable.dispose(); this.statusBarDisposable.dispose(); + this.badgeDisposable.dispose(); this.disposables = dispose(this.disposables); } } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index 5104baad51..f336011713 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -49,6 +49,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IViewsRegistry, IViewDescriptor, Extensions } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; +import { nextTick } from 'vs/base/common/process'; export interface ISpliceEvent { index: number; @@ -324,7 +325,7 @@ export class MainPanel extends ViewletPanel { } private onListSelectionChange(e: IListEvent): void { - if (e.elements.length > 0) { + if (e.browserEvent && e.elements.length > 0) { const scrollTop = this.list.scrollTop; this.viewModel.setVisibleRepositories(e.elements); this.list.scrollTop = scrollTop; @@ -332,7 +333,7 @@ export class MainPanel extends ViewletPanel { } private onListFocusChange(e: IListEvent): void { - if (e.elements.length > 0) { + if (e.browserEvent && e.elements.length > 0) { e.elements[0].focus(); } } @@ -797,7 +798,7 @@ export class RepositoryPanel extends ViewletPanel { const triggerValidation = () => validationDelayer.trigger(validate); - this.inputBox = new InputBox(this.inputBoxContainer, this.contextViewService, { flexibleHeight: true }); + this.inputBox = new InputBox(this.inputBoxContainer, this.contextViewService, { flexibleHeight: true, flexibleMaxHeight: 134 }); this.inputBox.setEnabled(this.isBodyVisible()); this._register(attachInputBoxStyler(this.inputBox, this.themeService)); this._register(this.inputBox); @@ -896,11 +897,8 @@ export class RepositoryPanel extends ViewletPanel { const listHeight = height - (editorHeight + 12 /* margin */); this.listContainer.style.height = `${listHeight}px`; this.list.layout(listHeight, width); - - toggleClass(this.inputBoxContainer, 'scroll', editorHeight >= 134); } else { addClass(this.inputBoxContainer, 'hidden'); - removeClass(this.inputBoxContainer, 'scroll'); this.listContainer.style.height = `${height}px`; this.list.layout(height, width); @@ -1105,6 +1103,9 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel { } } + private readonly _onDidChangeRepositories = new Emitter(); + private readonly onDidFinishStartup = Event.once(Event.debounce(this._onDidChangeRepositories.event, () => null, 1000)); + constructor( @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService, @@ -1133,6 +1134,9 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel { this.onDidChangeRepositories(); } })); + + this._register(this.onDidFinishStartup(this.onAfterStartup, this)); + this._register(this.viewsModel.onDidRemove(this.onDidHideView, this)); } create(parent: HTMLElement): void { @@ -1193,20 +1197,28 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel { if (alwaysShowProviders && repositoryCount > 0) { this.viewsModel.setVisible(MainPanel.ID, true); - } else if (!alwaysShowProviders && repositoryCount === 1) { - this.viewsModel.setVisible(MainPanel.ID, false); - } else if (this.repositoryCount < 2 && repositoryCount >= 2) { - this.viewsModel.setVisible(MainPanel.ID, true); - } else if (this.repositoryCount >= 2 && repositoryCount === 1) { - this.viewsModel.setVisible(MainPanel.ID, false); - } - - if (repositoryCount === 1) { - this.viewsModel.setVisible(this.viewDescriptors[0].id, true); } toggleClass(this.el, 'empty', repositoryCount === 0); this.repositoryCount = repositoryCount; + + this._onDidChangeRepositories.fire(); + } + + private onAfterStartup(): void { + if (this.repositoryCount > 0 && this.viewDescriptors.every(d => !this.viewsModel.isVisible(d.id))) { + this.viewsModel.setVisible(this.viewDescriptors[0].id, true); + } + } + + private onDidHideView(): void { + nextTick(() => { + if (this.repositoryCount > 0 && this.viewDescriptors.every(d => !this.viewsModel.isVisible(d.id))) { + const alwaysShowProviders = this.configurationService.getValue('scm.alwaysShowProviders') || false; + this.viewsModel.setVisible(MainPanel.ID, alwaysShowProviders || this.repositoryCount > 1); + this.viewsModel.setVisible(this.viewDescriptors[0].id, true); + } + }); } focus(): void { diff --git a/src/vs/workbench/contrib/search/browser/openFileHandler.ts b/src/vs/workbench/contrib/search/browser/openFileHandler.ts index 9e08445143..c69bc35dc7 100644 --- a/src/vs/workbench/contrib/search/browser/openFileHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openFileHandler.ts @@ -44,7 +44,7 @@ export class FileQuickOpenModel extends QuickOpenModel { } export class FileEntry extends EditorQuickOpenEntry { - private range: IRange | null; + private range: IRange | null = null; constructor( private resource: URI, @@ -112,7 +112,7 @@ export interface IOpenFileOptions { } export class OpenFileHandler extends QuickOpenHandler { - private options: IOpenFileOptions; + private options: IOpenFileOptions | undefined; private queryBuilder: QueryBuilder; private cacheState: CacheState; @@ -277,7 +277,7 @@ export class CacheState { private query: IFileQuery; private loadingPhase = LoadingPhase.Created; - private promise: Promise; + private promise: Promise | undefined; constructor(cacheQuery: (cacheKey: string) => IFileQuery, private doLoad: (query: IFileQuery) => Promise, private doDispose: (cacheKey: string) => Promise, private previous: CacheState | null) { this.query = cacheQuery(this._cacheKey); diff --git a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts index 0bd9ec0e2b..21bf12915f 100644 --- a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts @@ -27,7 +27,7 @@ import { Schemas } from 'vs/base/common/network'; import { IOpenerService } from 'vs/platform/opener/common/opener'; class SymbolEntry extends EditorQuickOpenEntry { - private bearingResolve: Promise; + private bearingResolve: Promise | undefined; constructor( private bearing: IWorkspaceSymbol, diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index 8f1534330e..a5b02af85f 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -14,7 +14,7 @@ import * as objects from 'vs/base/common/objects'; import { lcut } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { Range } from 'vs/editor/common/core/range'; -import { FindMatch, IModelDeltaDecoration, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { FindMatch, IModelDeltaDecoration, ITextModel, OverviewRulerLane, TrackedRangeStickiness, MinimapPosition } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IModelService } from 'vs/editor/common/services/modelService'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -22,11 +22,12 @@ import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; import { ReplacePattern } from 'vs/workbench/services/search/common/replace'; import { IFileMatch, IPatternInfo, ISearchComplete, ISearchProgressItem, ISearchService, ITextQuery, ITextSearchPreviewOptions, ITextSearchMatch, ITextSearchStats, resultIsMatch, ISearchRange, OneLineRange } from 'vs/workbench/services/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { overviewRulerFindMatchForeground } from 'vs/platform/theme/common/colorRegistry'; +import { overviewRulerFindMatchForeground, minimapFindMatch } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; import { editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { memoize } from 'vs/base/common/decorators'; export class Match { @@ -74,6 +75,7 @@ export class Match { return this._range; } + @memoize preview(): { before: string; inside: string; after: string; } { let before = this._oneLinePreviewText.substring(0, this._rangeInPreviewText.startColumn - 1), inside = this.getMatchString(), @@ -155,6 +157,10 @@ export class FileMatch extends Disposable implements IFileMatch { overviewRuler: { color: themeColorFromId(overviewRulerFindMatchForeground), position: OverviewRulerLane.Center + }, + minimap: { + color: themeColorFromId(minimapFindMatch), + position: MinimapPosition.Inline } }); @@ -164,6 +170,10 @@ export class FileMatch extends Disposable implements IFileMatch { overviewRuler: { color: themeColorFromId(overviewRulerFindMatchForeground), position: OverviewRulerLane.Center + }, + minimap: { + color: themeColorFromId(minimapFindMatch), + position: MinimapPosition.Inline } }); @@ -713,11 +723,19 @@ export class SearchResult extends Disposable { this.disposeMatches(); } - remove(matches: FileMatch | FileMatch[]): void { + remove(matches: FileMatch | FolderMatch | (FileMatch | FolderMatch)[]): void { if (!Array.isArray(matches)) { matches = [matches]; } + matches.forEach(m => { + if (m instanceof FolderMatch) { + m.clear(); + } + }); + + matches = matches.filter(m => m instanceof FileMatch); + const { byFolder, other } = this.groupFilesByFolder(matches); byFolder.forEach(matches => { if (!matches.length) { @@ -732,10 +750,6 @@ export class SearchResult extends Disposable { } } - removeFolder(match: FolderMatch): void { - match.clear(); - } - replace(match: FileMatch): Promise { return this.getFolderMatch(match.resource).replace(match); } diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts index 643185d822..285072f305 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts @@ -21,7 +21,7 @@ export class SnippetCompletion implements CompletionItem { label: string; detail: string; insertText: string; - documentation: MarkdownString; + documentation?: MarkdownString; range: IRange; sortText: string; kind: CompletionItemKind; diff --git a/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts index f823333aa5..71f884b8be 100644 --- a/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts +++ b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts @@ -31,8 +31,8 @@ export class TabCompletionController implements editorCommon.IEditorContribution private _hasSnippets: IContextKey; private _activeSnippets: Snippet[] = []; - private _enabled: boolean; - private _selectionListener: IDisposable; + private _enabled?: boolean; + private _selectionListener?: IDisposable; private readonly _configListener: IDisposable; constructor( diff --git a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts index 229ebb3627..2158771b13 100644 --- a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts +++ b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts @@ -32,8 +32,8 @@ class PartsSplash { private readonly _disposables = new DisposableStore(); - private _didChangeTitleBarStyle: boolean; - private _lastBaseTheme: string; + private _didChangeTitleBarStyle?: boolean; + private _lastBaseTheme?: string; private _lastBackground?: string; constructor( diff --git a/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts b/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts index 8124f75f21..256ff6fb24 100644 --- a/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts +++ b/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts @@ -15,6 +15,7 @@ import { endsWith } from 'vs/base/common/strings'; import { ITextFileService, } from 'vs/workbench/services/textfile/common/textfiles'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService'; +import { IWorkspaceInformation } from 'vs/platform/diagnostics/common/diagnosticsService'; const SshProtocolMatcher = /^([^@:]+@)?([^:]+):/; const SshUrlMatcher = /^([^@:]+@)?([^:]+):(.+)$/; @@ -175,10 +176,20 @@ export class WorkspaceStats implements IWorkbenchContribution { this.reportProxyStats(); const diagnosticsChannel = this.sharedProcessService.getChannel('diagnostics'); - diagnosticsChannel.call('reportWorkspaceStats', this.contextService.getWorkspace()); + diagnosticsChannel.call('reportWorkspaceStats', this.getWorkspaceInformation()); } - + private getWorkspaceInformation(): IWorkspaceInformation { + const workspace = this.contextService.getWorkspace(); + const state = this.contextService.getWorkbenchState(); + const id = this.workspaceStatsService.getTelemetryWorkspaceId(workspace, state); + return { + id: workspace.id, + telemetryId: id, + folders: workspace.folders, + configuration: workspace.configuration + }; + } private reportWorkspaceTags(tags: Tags): void { /* __GDPR__ diff --git a/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts b/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts index 07d1e3f519..a646548255 100644 --- a/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts +++ b/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts @@ -5,7 +5,7 @@ import * as crypto from 'crypto'; import { IFileService, IResolveFileResult, IFileStat } from 'vs/platform/files/common/files'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWindowService, IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { INotificationService, IPromptChoice } from 'vs/platform/notification/common/notification'; @@ -19,6 +19,7 @@ import { localize } from 'vs/nls'; import Severity from 'vs/base/common/severity'; import { joinPath } from 'vs/base/common/resources'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export type Tags = { [index: string]: boolean | number | string | undefined }; @@ -97,6 +98,12 @@ export const IWorkspaceStatsService = createDecorator('w export interface IWorkspaceStatsService { _serviceBrand: any; getTags(): Promise; + + /** + * Returns an id for the workspace, different from the id returned by the context service. A hash based + * on the folder uri or workspace configuration, not time-based, and undefined for empty workspaces. + */ + getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): string | undefined; } @@ -123,6 +130,28 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { return this._tags; } + public getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): string | undefined { + function createHash(uri: URI): string { + return crypto.createHash('sha1').update(uri.scheme === Schemas.file ? uri.fsPath : uri.toString()).digest('hex'); + } + + let workspaceId: string | undefined; + switch (state) { + case WorkbenchState.EMPTY: + workspaceId = undefined; + break; + case WorkbenchState.FOLDER: + workspaceId = createHash(workspace.folders[0].uri); + break; + case WorkbenchState.WORKSPACE: + if (workspace.configuration) { + workspaceId = createHash(workspace.configuration); + } + } + + return workspaceId; + } + /* __GDPR__FRAGMENT__ "WorkspaceTags" : { "workbench.filesToOpenOrCreate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -225,25 +254,7 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { const state = this.contextService.getWorkbenchState(); const workspace = this.contextService.getWorkspace(); - function createHash(uri: URI): string { - return crypto.createHash('sha1').update(uri.scheme === Schemas.file ? uri.fsPath : uri.toString()).digest('hex'); - } - - let workspaceId: string | undefined; - switch (state) { - case WorkbenchState.EMPTY: - workspaceId = undefined; - break; - case WorkbenchState.FOLDER: - workspaceId = createHash(workspace.folders[0].uri); - break; - case WorkbenchState.WORKSPACE: - if (workspace.configuration) { - workspaceId = createHash(workspace.configuration); - } - } - - tags['workspace.id'] = workspaceId; + tags['workspace.id'] = this.getTelemetryWorkspaceId(workspace, state); const { filesToOpenOrCreate, filesToDiff } = configuration; tags['workbench.filesToOpenOrCreate'] = filesToOpenOrCreate && filesToOpenOrCreate.length || 0; @@ -503,3 +514,5 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { return arr.some(v => v.search(regEx) > -1) || undefined; } } + +registerSingleton(IWorkspaceStatsService, WorkspaceStatsService, true); diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 0dd1c99c96..21e422c2d1 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -73,6 +73,11 @@ import { RunAutomaticTasks } from 'vs/workbench/contrib/tasks/browser/runAutomat import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { format } from 'vs/base/common/jsonFormatter'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { applyEdits } from 'vs/base/common/jsonEdit'; +import { ITextEditor } from 'vs/workbench/common/editor'; +import { ITextEditorSelection } from 'vs/platform/editor/common/editor'; export namespace ConfigureTaskAction { export const ID = 'workbench.action.tasks.configureTaskRunner'; @@ -220,7 +225,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @ITerminalInstanceService private readonly terminalInstanceService: ITerminalInstanceService, - @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, + @ITextModelService private readonly textModelResolverService: ITextModelService ) { super(); @@ -300,7 +306,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); CommandsRegistry.registerCommand('workbench.action.tasks.reRunTask', (accessor, arg) => { - this.reRunTaskCommand(arg); + this.reRunTaskCommand(); }); CommandsRegistry.registerCommand('workbench.action.tasks.restartTask', (accessor, arg) => { @@ -502,7 +508,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (!this.versionAndEngineCompatible(filter)) { return Promise.resolve([]); } - return this.getGroupedTasks().then((map) => { + return this.getGroupedTasks(filter ? filter.type : undefined).then((map) => { if (!filter || !filter.type) { return map.all(); } @@ -769,6 +775,60 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return false; } + private openEditorAtTask(resource: URI | undefined, task: TaskConfig.CustomTask | TaskConfig.ConfiguringTask | string | undefined): Promise { + if (resource === undefined) { + return Promise.resolve(undefined); + } + let selection: ITextEditorSelection | undefined; + return this.fileService.readFile(resource).then(content => content.value).then(async content => { + if (!content) { + return undefined; + } + if (task) { + const contentValue = content.toString(); + let stringValue: string; + if (typeof task === 'string') { + stringValue = task; + } else { + const model = (await this.textModelResolverService.createModelReference(resource)).object.textEditorModel; + const { tabSize, insertSpaces } = model.getOptions(); + const eol = model.getEOL(); + const edits = format(JSON.stringify(task), undefined, { eol, tabSize, insertSpaces }); + let stringified = applyEdits(JSON.stringify(task), edits); + const regex = new RegExp(eol + '\\t', 'g'); + stringified = stringified.replace(regex, eol + '\t\t\t'); + const twoTabs = '\t\t'; + stringValue = twoTabs + stringified.slice(0, stringified.length - 1) + twoTabs + stringified.slice(stringified.length - 1); + } + + const index = contentValue.indexOf(stringValue); + let startLineNumber = 1; + for (let i = 0; i < index; i++) { + if (contentValue.charAt(i) === '\n') { + startLineNumber++; + } + } + let endLineNumber = startLineNumber; + for (let i = 0; i < stringValue.length; i++) { + if (stringValue.charAt(i) === '\n') { + endLineNumber++; + } + } + selection = startLineNumber > 1 ? { startLineNumber, startColumn: startLineNumber === endLineNumber ? 4 : 3, endLineNumber, endColumn: startLineNumber === endLineNumber ? undefined : 4 } : undefined; + } + + return this.editorService.openEditor({ + resource, + options: { + pinned: false, + forceReload: true, // because content might have changed + selection, + revealInCenterIfOutsideViewport: !!selection + } + }); + }); + } + public customize(task: ContributedTask | CustomTask, properties?: CustomizationProperties, openConfig?: boolean): Promise { const workspaceFolder = task.getWorkspaceFolder(); if (!workspaceFolder) { @@ -796,6 +856,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (task.configurationProperties.problemMatchers && task.configurationProperties.problemMatchers.length > 0 && Types.isStringArray(task.configurationProperties.problemMatchers)) { toCustomize.problemMatcher = task.configurationProperties.problemMatchers; } + if (task.configurationProperties.group) { + toCustomize.group = task.configurationProperties.group; + } } if (!toCustomize) { return Promise.resolve(undefined); @@ -864,14 +927,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer */ this.telemetryService.publicLog(AbstractTaskService.CustomizationTelemetryEventName, event); if (openConfig) { - let resource = workspaceFolder.toResource('.vscode/tasks.json'); - this.editorService.openEditor({ - resource, - options: { - pinned: false, - forceReload: true // because content might have changed - } - }); + this.openEditorAtTask(workspaceFolder.toResource('.vscode/tasks.json'), toCustomize); } }); } @@ -893,12 +949,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } else { resource = (this._workspaceFolders && (this._workspaceFolders.length > 0)) ? this._workspaceFolders[0].toResource('.vscode/tasks.json') : undefined; } - return this.editorService.openEditor({ - resource, - options: { - pinned: false - } - }).then(() => undefined); + return this.openEditorAtTask(resource, task ? task._label : undefined).then(() => undefined); } private createRunnableTask(tasks: TaskMap, group: TaskGroup): { task: Task; resolver: ITaskResolver } | undefined { @@ -1114,7 +1165,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer protected abstract getTaskSystem(): ITaskSystem; - private getGroupedTasks(): Promise { + private getGroupedTasks(type?: string): Promise { return Promise.all([this.extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'), TaskDefinitionRegistry.onReady()]).then(() => { let validTypes: IStringDictionary = Object.create(null); TaskDefinitionRegistry.all().forEach(definition => validTypes[definition.taskType] = true); @@ -1149,10 +1200,12 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } }; if (this.schemaVersion === JsonSchemaVersion.V2_0_0 && this._providers.size > 0) { - this._providers.forEach((provider) => { - counter++; - provider.provideTasks(validTypes).then(done, error); - }); + for (const [handle, provider] of this._providers) { + if ((type === undefined) || (type === this._providerTypes.get(handle))) { + counter++; + provider.provideTasks(validTypes).then(done, error); + } + } } else { resolve(result); } @@ -1757,7 +1810,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); } - private reRunTaskCommand(arg?: any): void { + private reRunTaskCommand(): void { if (!this.canRunCommand()) { return; } diff --git a/src/vs/workbench/contrib/tasks/browser/taskService.ts b/src/vs/workbench/contrib/tasks/browser/taskService.ts index 8833b78fbb..bd5852c872 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskService.ts @@ -9,7 +9,8 @@ import { ITaskSystem } from 'vs/workbench/contrib/tasks/common/taskSystem'; import { ExecutionEngine, TaskRunSource } from 'vs/workbench/contrib/tasks/common/tasks'; import { TerminalTaskSystem } from './terminalTaskSystem'; import { AbstractTaskService, WorkspaceFolderConfigurationResult } from 'vs/workbench/contrib/tasks/browser/abstractTaskService'; -import { TaskFilter } from 'vs/workbench/contrib/tasks/common/taskService'; +import { TaskFilter, ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class TaskService extends AbstractTaskService { private static readonly ProcessTaskSystemSupportMessage = nls.localize('taskService.processTaskSystem', 'Process task system is not support in the web.'); @@ -48,4 +49,6 @@ export class TaskService extends AbstractTaskService { protected versionAndEngineCompatible(filter?: TaskFilter): boolean { return this.executionEngine === ExecutionEngine.Terminal; } -} \ No newline at end of file +} + +registerSingleton(ITaskService, TaskService, true); diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index f4587c05cd..c06a81e08a 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -285,7 +285,7 @@ export class TerminalTaskSystem implements ITaskSystem { } return new Promise((resolve) => { - activeTerminal.terminal.rendererExit(result); + // activeTerminal.terminal.rendererExit(result); resolve(); }); } @@ -472,7 +472,7 @@ export class TerminalTaskSystem implements ITaskSystem { const resolvedVariables = this.resolveVariablesFromSet(systemInfo, workspaceFolder, task, variables); return resolvedVariables.then((resolvedVariables) => { - const isCustomExecution = (task.command.runtime === RuntimeType.CustomExecution) || (task.command.runtime === RuntimeType.CustomExecution2); + const isCustomExecution = (task.command.runtime === RuntimeType.CustomExecution2); if (resolvedVariables && (task.command !== undefined) && task.command.runtime && (isCustomExecution || (task.command.name !== undefined))) { this.currentTask.resolvedVariables = resolvedVariables; return this.executeInTerminal(task, trigger, new VariableResolver(workspaceFolder, systemInfo, resolvedVariables.variables, this.configurationResolverService), workspaceFolder); @@ -556,9 +556,7 @@ export class TerminalTaskSystem implements ITaskSystem { let processStartedSignaled = false; terminal.processReady.then(() => { if (!processStartedSignaled) { - if (task.command.runtime !== RuntimeType.CustomExecution) { - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); - } + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); processStartedSignaled = true; } }, (_error) => { @@ -608,9 +606,7 @@ export class TerminalTaskSystem implements ITaskSystem { processStartedSignaled = true; } - if (task.command.runtime !== RuntimeType.CustomExecution) { - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode)); - } + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode)); for (let i = 0; i < eventCounter; i++) { let event = TaskEvent.create(TaskEventKind.Inactive, task); @@ -635,9 +631,7 @@ export class TerminalTaskSystem implements ITaskSystem { let processStartedSignaled = false; terminal.processReady.then(() => { if (!processStartedSignaled) { - if (task.command.runtime !== RuntimeType.CustomExecution) { - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); - } + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); processStartedSignaled = true; } }, (_error) => { @@ -690,9 +684,8 @@ export class TerminalTaskSystem implements ITaskSystem { this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal.processId!)); processStartedSignaled = true; } - if (task.command.runtime !== RuntimeType.CustomExecution) { - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode)); - } + + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode)); this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Inactive, task)); this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.End, task)); resolve({ exitCode }); @@ -773,7 +766,7 @@ export class TerminalTaskSystem implements ITaskSystem { let terminalName = this.createTerminalName(task, workspaceFolder); let originalCommand = task.command.name; if (isShellCommand) { - const defaultConfig = await this.terminalInstanceService.getDefaultShellAndArgs(platform); + const defaultConfig = await this.terminalInstanceService.getDefaultShellAndArgs(true, platform); shellLaunchConfig = { name: terminalName, executable: defaultConfig.shell, args: defaultConfig.args, waitOnExit }; let shellSpecified: boolean = false; let shellOptions: ShellConfiguration | undefined = task.command.options && task.command.options.shell; @@ -845,7 +838,7 @@ export class TerminalTaskSystem implements ITaskSystem { } } } else { - let commandExecutable = ((task.command.runtime !== RuntimeType.CustomExecution) && (task.command.runtime !== RuntimeType.CustomExecution2)) ? CommandString.value(command) : undefined; + let commandExecutable = (task.command.runtime !== RuntimeType.CustomExecution2) ? CommandString.value(command) : undefined; let executable = !isShellCommand ? this.resolveVariable(variableResolver, '${' + TerminalTaskSystem.ProcessVarName + '}') : commandExecutable; @@ -917,14 +910,7 @@ export class TerminalTaskSystem implements ITaskSystem { let args: CommandString[] | undefined; let launchConfigs: IShellLaunchConfig | undefined; - if (task.command.runtime === RuntimeType.CustomExecution) { - this.currentTask.shellLaunchConfig = launchConfigs = { - isRendererOnly: true, - waitOnExit, - name: this.createTerminalName(task, workspaceFolder), - initialText: task.command.presentation && task.command.presentation.echo ? `\x1b[1m> Executing task: ${task._label} <\x1b[0m\n` : undefined - }; - } else if (task.command.runtime === RuntimeType.CustomExecution2) { + if (task.command.runtime === RuntimeType.CustomExecution2) { this.currentTask.shellLaunchConfig = launchConfigs = { isExtensionTerminal: true, waitOnExit, @@ -1145,7 +1131,7 @@ export class TerminalTaskSystem implements ITaskSystem { private collectCommandVariables(variables: Set, command: CommandConfiguration, task: CustomTask | ContributedTask): void { // The custom execution should have everything it needs already as it provided // the callback. - if ((command.runtime === RuntimeType.CustomExecution) || (command.runtime === RuntimeType.CustomExecution2)) { + if (command.runtime === RuntimeType.CustomExecution2) { return; } diff --git a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts index c6d0b20e92..b17875dffb 100644 --- a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts +++ b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import * as Objects from 'vs/base/common/objects'; import * as Strings from 'vs/base/common/strings'; import * as Assert from 'vs/base/common/assert'; -import { join } from 'vs/base/common/path'; +import { join, normalize } from 'vs/base/common/path'; import * as Types from 'vs/base/common/types'; import * as UUID from 'vs/base/common/uuid'; import * as Platform from 'vs/base/common/platform'; @@ -220,6 +220,7 @@ export async function getResource(filename: string, matcher: ProblemMatcher, fil if (fullPath[0] !== '/') { fullPath = '/' + fullPath; } + fullPath = normalize(fullPath); if (matcher.uriProvider !== undefined) { return matcher.uriProvider(fullPath); } else { diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index b3a2ae41a5..917fe50ade 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -273,8 +273,7 @@ export namespace PresentationOptions { export enum RuntimeType { Shell = 1, Process = 2, - CustomExecution = 3, - CustomExecution2 = 4 + CustomExecution2 = 3 } export namespace RuntimeType { @@ -284,8 +283,6 @@ export namespace RuntimeType { return RuntimeType.Shell; case 'process': return RuntimeType.Process; - case 'customExecution': - return RuntimeType.CustomExecution; case 'customExecution2': return RuntimeType.CustomExecution2; default: @@ -663,10 +660,6 @@ export class CustomTask extends CommonTask { type = 'process'; break; - case RuntimeType.CustomExecution: - type = 'customExecution'; - break; - case RuntimeType.CustomExecution2: type = 'customExecution2'; break; diff --git a/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts b/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts index e55fed1c53..005c364ab1 100644 --- a/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts @@ -13,7 +13,8 @@ import * as TaskConfig from '../common/taskConfiguration'; import { ProcessTaskSystem } from 'vs/workbench/contrib/tasks/node/processTaskSystem'; import { ProcessRunnerDetector } from 'vs/workbench/contrib/tasks/node/processRunnerDetector'; import { AbstractTaskService } from 'vs/workbench/contrib/tasks/browser/abstractTaskService'; -import { TaskFilter } from 'vs/workbench/contrib/tasks/common/taskService'; +import { TaskFilter, ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; interface WorkspaceFolderConfigurationResult { workspaceFolder: IWorkspaceFolder; @@ -132,4 +133,6 @@ export class TaskService extends AbstractTaskService { } return result; } -} \ No newline at end of file +} + +registerSingleton(ITaskService, TaskService, true); diff --git a/src/vs/workbench/contrib/tasks/node/processRunnerDetector.ts b/src/vs/workbench/contrib/tasks/node/processRunnerDetector.ts index ad382d24c9..4edf181b1a 100644 --- a/src/vs/workbench/contrib/tasks/node/processRunnerDetector.ts +++ b/src/vs/workbench/contrib/tasks/node/processRunnerDetector.ts @@ -59,9 +59,9 @@ class RegexpTaskMatcher implements TaskDetectorMatcher { } class GruntTaskMatcher implements TaskDetectorMatcher { - private tasksStart: boolean; - private tasksEnd: boolean; - private descriptionOffset: number | null; + private tasksStart!: boolean; + private tasksEnd!: boolean; + private descriptionOffset!: number | null; init() { this.tasksStart = false; @@ -179,7 +179,7 @@ export class ProcessRunnerDetector { commandExecutable, isShellCommand, config.matcher, ProcessRunnerDetector.DefaultProblemMatchers, list)); } else { if (detectSpecific) { - let detectorPromise: Promise; + let detectorPromise: Promise; if ('gulp' === detectSpecific) { detectorPromise = this.tryDetectGulp(this._workspaceRoot, list); } else if ('jake' === detectSpecific) { @@ -229,7 +229,7 @@ export class ProcessRunnerDetector { return result; } - private tryDetectGulp(workspaceFolder: IWorkspaceFolder, list: boolean): Promise { + private tryDetectGulp(workspaceFolder: IWorkspaceFolder, list: boolean): Promise { return Promise.resolve(this.fileService.resolve(workspaceFolder.toResource('gulpfile.js'))).then((stat) => { // TODO@Dirk (https://github.com/Microsoft/vscode/issues/29454) let config = ProcessRunnerDetector.detectorConfig('gulp'); let process = new LineProcess('gulp', [config.arg, '--no-color'], true, { cwd: this._cwd }); @@ -239,7 +239,7 @@ export class ProcessRunnerDetector { }); } - private tryDetectGrunt(workspaceFolder: IWorkspaceFolder, list: boolean): Promise { + private tryDetectGrunt(workspaceFolder: IWorkspaceFolder, list: boolean): Promise { return Promise.resolve(this.fileService.resolve(workspaceFolder.toResource('Gruntfile.js'))).then((stat) => { // TODO@Dirk (https://github.com/Microsoft/vscode/issues/29454) let config = ProcessRunnerDetector.detectorConfig('grunt'); let process = new LineProcess('grunt', [config.arg, '--no-color'], true, { cwd: this._cwd }); @@ -249,7 +249,7 @@ export class ProcessRunnerDetector { }); } - private tryDetectJake(workspaceFolder: IWorkspaceFolder, list: boolean): Promise { + private tryDetectJake(workspaceFolder: IWorkspaceFolder, list: boolean): Promise { let run = () => { let config = ProcessRunnerDetector.detectorConfig('jake'); let process = new LineProcess('jake', [config.arg], true, { cwd: this._cwd }); diff --git a/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts b/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts index 9c95075035..aa4a7c69dd 100644 --- a/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts @@ -280,7 +280,7 @@ export class ProcessTaskSystem implements ITaskSystem { this.childProcessEnded(); watchingProblemMatcher.done(); watchingProblemMatcher.dispose(); - if (processStartedSignaled && task.command.runtime !== RuntimeType.CustomExecution) { + if (processStartedSignaled) { this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, success.cmdCode!)); } toDispose = dispose(toDispose!); @@ -336,7 +336,7 @@ export class ProcessTaskSystem implements ITaskSystem { startStopProblemMatcher.done(); startStopProblemMatcher.dispose(); this.checkTerminated(task, success); - if (processStartedSignaled && task.command.runtime !== RuntimeType.CustomExecution) { + if (processStartedSignaled) { this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, success.cmdCode!)); } this._onDidStateChange.fire(inactiveEvent); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts b/src/vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon.ts similarity index 56% rename from src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts rename to src/vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon.ts index d40acbaf90..9a75ad3a02 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts +++ b/src/vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon.ts @@ -3,9 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Terminal, IMarker } from 'xterm'; -import { ITerminalCommandTracker } from 'vs/workbench/contrib/terminal/common/terminal'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { Terminal, IMarker, ITerminalAddon } from 'xterm'; +import { ICommandTracker } from 'vs/workbench/contrib/terminal/common/terminal'; /** * The minimum size of the prompt in which to assume the line is a command. @@ -22,15 +21,15 @@ export const enum ScrollPosition { Middle } -export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposable { +export class CommandTrackerAddon implements ICommandTracker, ITerminalAddon { private _currentMarker: IMarker | Boundary = Boundary.Bottom; private _selectionStart: IMarker | Boundary | null = null; private _isDisposable: boolean = false; + private _terminal: Terminal | undefined; - constructor( - private _xterm: Terminal - ) { - this._xterm.onKey(e => this._onKey(e.key)); + public activate(terminal: Terminal): void { + this._terminal = terminal; + terminal.onKey(e => this._onKey(e.key)); } public dispose(): void { @@ -48,116 +47,138 @@ export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposa } private _onEnter(): void { - if (this._xterm.buffer.cursorX >= MINIMUM_PROMPT_LENGTH) { - this._xterm.addMarker(0); + if (!this._terminal) { + return; + } + if (this._terminal.buffer.cursorX >= MINIMUM_PROMPT_LENGTH) { + this._terminal.addMarker(0); } } public scrollToPreviousCommand(scrollPosition: ScrollPosition = ScrollPosition.Top, retainSelection: boolean = false): void { + if (!this._terminal) { + return; + } if (!retainSelection) { this._selectionStart = null; } let markerIndex; if (this._currentMarker === Boundary.Bottom) { - markerIndex = this._xterm.markers.length - 1; + markerIndex = this._terminal.markers.length - 1; } else if (this._currentMarker === Boundary.Top) { markerIndex = -1; } else if (this._isDisposable) { - markerIndex = this._findPreviousCommand(); + markerIndex = this._findPreviousCommand(this._terminal); this._currentMarker.dispose(); this._isDisposable = false; } else { - markerIndex = this._xterm.markers.indexOf(this._currentMarker) - 1; + markerIndex = this._terminal.markers.indexOf(this._currentMarker) - 1; } if (markerIndex < 0) { this._currentMarker = Boundary.Top; - this._xterm.scrollToTop(); + this._terminal.scrollToTop(); return; } - this._currentMarker = this._xterm.markers[markerIndex]; + this._currentMarker = this._terminal.markers[markerIndex]; this._scrollToMarker(this._currentMarker, scrollPosition); } public scrollToNextCommand(scrollPosition: ScrollPosition = ScrollPosition.Top, retainSelection: boolean = false): void { + if (!this._terminal) { + return; + } if (!retainSelection) { this._selectionStart = null; } let markerIndex; if (this._currentMarker === Boundary.Bottom) { - markerIndex = this._xterm.markers.length; + markerIndex = this._terminal.markers.length; } else if (this._currentMarker === Boundary.Top) { markerIndex = 0; } else if (this._isDisposable) { - markerIndex = this._findNextCommand(); + markerIndex = this._findNextCommand(this._terminal); this._currentMarker.dispose(); this._isDisposable = false; } else { - markerIndex = this._xterm.markers.indexOf(this._currentMarker) + 1; + markerIndex = this._terminal.markers.indexOf(this._currentMarker) + 1; } - if (markerIndex >= this._xterm.markers.length) { + if (markerIndex >= this._terminal.markers.length) { this._currentMarker = Boundary.Bottom; - this._xterm.scrollToBottom(); + this._terminal.scrollToBottom(); return; } - this._currentMarker = this._xterm.markers[markerIndex]; + this._currentMarker = this._terminal.markers[markerIndex]; this._scrollToMarker(this._currentMarker, scrollPosition); } private _scrollToMarker(marker: IMarker, position: ScrollPosition): void { + if (!this._terminal) { + return; + } let line = marker.line; if (position === ScrollPosition.Middle) { - line = Math.max(line - Math.floor(this._xterm.rows / 2), 0); + line = Math.max(line - Math.floor(this._terminal.rows / 2), 0); } - this._xterm.scrollToLine(line); + this._terminal.scrollToLine(line); } public selectToPreviousCommand(): void { + if (!this._terminal) { + return; + } if (this._selectionStart === null) { this._selectionStart = this._currentMarker; } this.scrollToPreviousCommand(ScrollPosition.Middle, true); - this._selectLines(this._currentMarker, this._selectionStart); + this._selectLines(this._terminal, this._currentMarker, this._selectionStart); } public selectToNextCommand(): void { + if (!this._terminal) { + return; + } if (this._selectionStart === null) { this._selectionStart = this._currentMarker; } this.scrollToNextCommand(ScrollPosition.Middle, true); - this._selectLines(this._currentMarker, this._selectionStart); + this._selectLines(this._terminal, this._currentMarker, this._selectionStart); } public selectToPreviousLine(): void { + if (!this._terminal) { + return; + } if (this._selectionStart === null) { this._selectionStart = this._currentMarker; } - - this.scrollToPreviousLine(ScrollPosition.Middle, true); - this._selectLines(this._currentMarker, this._selectionStart); + this.scrollToPreviousLine(this._terminal, ScrollPosition.Middle, true); + this._selectLines(this._terminal, this._currentMarker, this._selectionStart); } public selectToNextLine(): void { + if (!this._terminal) { + return; + } if (this._selectionStart === null) { this._selectionStart = this._currentMarker; } - - this.scrollToNextLine(ScrollPosition.Middle, true); - this._selectLines(this._currentMarker, this._selectionStart); + this.scrollToNextLine(this._terminal, ScrollPosition.Middle, true); + this._selectLines(this._terminal, this._currentMarker, this._selectionStart); } - private _selectLines(start: IMarker | Boundary, end: IMarker | Boundary | null): void { + private _selectLines(xterm: Terminal, start: IMarker | Boundary, end: IMarker | Boundary | null): void { if (end === null) { end = Boundary.Bottom; } - let startLine = this._getLine(start); - let endLine = this._getLine(end); + let startLine = this._getLine(xterm, start); + let endLine = this._getLine(xterm, end); if (startLine > endLine) { const temp = startLine; @@ -169,13 +190,13 @@ export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposa // command in the selection for the current command endLine -= 1; - this._xterm.selectLines(startLine, endLine); + xterm.selectLines(startLine, endLine); } - private _getLine(marker: IMarker | Boundary): number { + private _getLine(xterm: Terminal, marker: IMarker | Boundary): number { // Use the _second last_ row as the last row is likely the prompt if (marker === Boundary.Bottom) { - return this._xterm.buffer.baseY + this._xterm.rows - 1; + return xterm.buffer.baseY + xterm.rows - 1; } if (marker === Boundary.Top) { @@ -185,74 +206,74 @@ export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposa return marker.line; } - public scrollToPreviousLine(scrollPosition: ScrollPosition = ScrollPosition.Top, retainSelection: boolean = false): void { + public scrollToPreviousLine(xterm: Terminal, scrollPosition: ScrollPosition = ScrollPosition.Top, retainSelection: boolean = false): void { if (!retainSelection) { this._selectionStart = null; } if (this._currentMarker === Boundary.Top) { - this._xterm.scrollToTop(); + xterm.scrollToTop(); return; } if (this._currentMarker === Boundary.Bottom) { - this._currentMarker = this._xterm.addMarker(this._getOffset() - 1); + this._currentMarker = xterm.addMarker(this._getOffset(xterm) - 1); } else { - const offset = this._getOffset(); + const offset = this._getOffset(xterm); if (this._isDisposable) { this._currentMarker.dispose(); } - this._currentMarker = this._xterm.addMarker(offset - 1); + this._currentMarker = xterm.addMarker(offset - 1); } this._isDisposable = true; this._scrollToMarker(this._currentMarker, scrollPosition); } - public scrollToNextLine(scrollPosition: ScrollPosition = ScrollPosition.Top, retainSelection: boolean = false): void { + public scrollToNextLine(xterm: Terminal, scrollPosition: ScrollPosition = ScrollPosition.Top, retainSelection: boolean = false): void { if (!retainSelection) { this._selectionStart = null; } if (this._currentMarker === Boundary.Bottom) { - this._xterm.scrollToBottom(); + xterm.scrollToBottom(); return; } if (this._currentMarker === Boundary.Top) { - this._currentMarker = this._xterm.addMarker(this._getOffset() + 1); + this._currentMarker = xterm.addMarker(this._getOffset(xterm) + 1); } else { - const offset = this._getOffset(); + const offset = this._getOffset(xterm); if (this._isDisposable) { this._currentMarker.dispose(); } - this._currentMarker = this._xterm.addMarker(offset + 1); + this._currentMarker = xterm.addMarker(offset + 1); } this._isDisposable = true; this._scrollToMarker(this._currentMarker, scrollPosition); } - private _getOffset(): number { + private _getOffset(xterm: Terminal): number { if (this._currentMarker === Boundary.Bottom) { return 0; } else if (this._currentMarker === Boundary.Top) { - return 0 - (this._xterm.buffer.baseY + this._xterm.buffer.cursorY); + return 0 - (xterm.buffer.baseY + xterm.buffer.cursorY); } else { - let offset = this._getLine(this._currentMarker); - offset -= this._xterm.buffer.baseY + this._xterm.buffer.cursorY; + let offset = this._getLine(xterm, this._currentMarker); + offset -= xterm.buffer.baseY + xterm.buffer.cursorY; return offset; } } - private _findPreviousCommand(): number { + private _findPreviousCommand(xterm: Terminal): number { if (this._currentMarker === Boundary.Top) { return 0; } else if (this._currentMarker === Boundary.Bottom) { - return this._xterm.markers.length - 1; + return xterm.markers.length - 1; } let i; - for (i = this._xterm.markers.length - 1; i >= 0; i--) { - if (this._xterm.markers[i].line < this._currentMarker.line) { + for (i = xterm.markers.length - 1; i >= 0; i--) { + if (xterm.markers[i].line < this._currentMarker.line) { return i; } } @@ -260,20 +281,20 @@ export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposa return -1; } - private _findNextCommand(): number { + private _findNextCommand(xterm: Terminal): number { if (this._currentMarker === Boundary.Top) { return 0; } else if (this._currentMarker === Boundary.Bottom) { - return this._xterm.markers.length - 1; + return xterm.markers.length - 1; } let i; - for (i = 0; i < this._xterm.markers.length; i++) { - if (this._xterm.markers[i].line > this._currentMarker.line) { + for (i = 0; i < xterm.markers.length; i++) { + if (xterm.markers[i].line > this._currentMarker.line) { return i; } } - return this._xterm.markers.length; + return xterm.markers.length; } } diff --git a/src/vs/workbench/contrib/terminal/browser/addons/navigationModeAddon.ts b/src/vs/workbench/contrib/terminal/browser/addons/navigationModeAddon.ts index a47c6e6989..cac8c3a990 100644 --- a/src/vs/workbench/contrib/terminal/browser/addons/navigationModeAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/addons/navigationModeAddon.ts @@ -9,7 +9,7 @@ import { addDisposableListener } from 'vs/base/browser/dom'; import { INavigationMode } from 'vs/workbench/contrib/terminal/common/terminal'; export class NavigationModeAddon implements INavigationMode, ITerminalAddon { - private _terminal: Terminal; + private _terminal: Terminal | undefined; constructor( private _navigationModeContextKey: IContextKey @@ -22,11 +22,18 @@ export class NavigationModeAddon implements INavigationMode, ITerminalAddon { dispose() { } exitNavigationMode(): void { + if (!this._terminal) { + return; + } this._terminal.scrollToBottom(); this._terminal.focus(); } focusPreviousLine(): void { + if (!this._terminal) { + return; + } + // Focus previous row if a row is already focused if (document.activeElement && document.activeElement.parentElement && document.activeElement.parentElement.classList.contains('xterm-accessibility-tree')) { const element = document.activeElement.previousElementSibling; @@ -66,6 +73,10 @@ export class NavigationModeAddon implements INavigationMode, ITerminalAddon { } focusNextLine(): void { + if (!this._terminal) { + return; + } + // Focus previous row if a row is already focused if (document.activeElement && document.activeElement.parentElement && document.activeElement.parentElement.classList.contains('xterm-accessibility-tree')) { const element = document.activeElement.nextElementSibling; @@ -103,4 +114,4 @@ export class NavigationModeAddon implements INavigationMode, ITerminalAddon { }); this._navigationModeContextKey.set(true); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 4c37451867..9ced7b2156 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -71,6 +71,21 @@ configurationRegistry.registerConfiguration({ title: nls.localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"), type: 'object', properties: { + 'terminal.integrated.automationShell.linux': { + markdownDescription: nls.localize('terminal.integrated.automationShell.linux', "A path that when set will override {0} and ignore {1} and {2} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.linux`', '`shellArgs`', '`env`'), + type: ['string', 'null'], + default: null + }, + 'terminal.integrated.automationShell.osx': { + markdownDescription: nls.localize('terminal.integrated.automationShell.osx', "A path that when set will override {0} and ignore {1} and {2} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.osx`', '`shellArgs`', '`env`'), + type: ['string', 'null'], + default: null + }, + 'terminal.integrated.automationShell.windows': { + markdownDescription: nls.localize('terminal.integrated.automationShell.windows', "A path that when set will override {0} and ignore {1} and {2} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.windows`', '`shellArgs`', '`env`'), + type: ['string', 'null'], + default: null + }, 'terminal.integrated.shellArgs.linux': { markdownDescription: nls.localize('terminal.integrated.shellArgs.linux', "The command line arguments to use when on the Linux terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), type: 'array', @@ -450,11 +465,11 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneDownTe actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollToPreviousCommandAction, ScrollToPreviousCommandAction.ID, ScrollToPreviousCommandAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll To Previous Command', category); +}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate())), 'Terminal: Scroll To Previous Command', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollToNextCommandAction, ScrollToNextCommandAction.ID, ScrollToNextCommandAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll To Next Command', category); +}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate())), 'Terminal: Scroll To Next Command', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectToPreviousCommandAction, SelectToPreviousCommandAction.ID, SelectToPreviousCommandAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 7ca9f04a15..0b78c3477f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -30,10 +30,10 @@ export interface ITerminalInstanceService { createWindowsShellHelper(shellProcessId: number, instance: ITerminalInstance, xterm: XTermTerminal): IWindowsShellHelper; createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): ITerminalChildProcess; - getDefaultShellAndArgs(platformOverride?: Platform): Promise<{ shell: string, args: string[] | string | undefined }>; + getDefaultShellAndArgs(useAutomationShell: boolean, platformOverride?: Platform): Promise<{ shell: string, args: string[] | string | undefined }>; getMainProcessParentEnv(): Promise; } export interface IBrowserTerminalConfigHelper extends ITerminalConfigHelper { - panelContainer: HTMLElement; + panelContainer: HTMLElement | undefined; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 6f5d95122a..ea096daffc 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -1168,7 +1168,7 @@ export class ScrollToPreviousCommandAction extends Action { public run(): Promise { const instance = this.terminalService.getActiveInstance(); - if (instance) { + if (instance && instance.commandTracker) { instance.commandTracker.scrollToPreviousCommand(); instance.focus(); } @@ -1189,7 +1189,7 @@ export class ScrollToNextCommandAction extends Action { public run(): Promise { const instance = this.terminalService.getActiveInstance(); - if (instance) { + if (instance && instance.commandTracker) { instance.commandTracker.scrollToNextCommand(); instance.focus(); } @@ -1210,7 +1210,7 @@ export class SelectToPreviousCommandAction extends Action { public run(): Promise { const instance = this.terminalService.getActiveInstance(); - if (instance) { + if (instance && instance.commandTracker) { instance.commandTracker.selectToPreviousCommand(); instance.focus(); } @@ -1231,7 +1231,7 @@ export class SelectToNextCommandAction extends Action { public run(): Promise { const instance = this.terminalService.getActiveInstance(); - if (instance) { + if (instance && instance.commandTracker) { instance.commandTracker.selectToNextCommand(); instance.focus(); } @@ -1252,7 +1252,7 @@ export class SelectToPreviousLineAction extends Action { public run(): Promise { const instance = this.terminalService.getActiveInstance(); - if (instance) { + if (instance && instance.commandTracker) { instance.commandTracker.selectToPreviousLine(); instance.focus(); } @@ -1273,7 +1273,7 @@ export class SelectToNextLineAction extends Action { public run(): Promise { const instance = this.terminalService.getActiveInstance(); - if (instance) { + if (instance && instance.commandTracker) { instance.commandTracker.selectToNextLine(); instance.focus(); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts index cf1442ae51..e9797ea59c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts @@ -26,11 +26,11 @@ const MAXIMUM_FONT_SIZE = 25; * specific test cases can be written. */ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { - public panelContainer: HTMLElement; + public panelContainer: HTMLElement | undefined; - private _charMeasureElement: HTMLElement; - private _lastFontMeasurement: ITerminalFont; - public config: ITerminalConfiguration; + private _charMeasureElement: HTMLElement | undefined; + private _lastFontMeasurement: ITerminalFont | undefined; + public config!: ITerminalConfiguration; private readonly _onWorkspacePermissionsChanged = new Emitter(); public get onWorkspacePermissionsChanged(): Event { return this._onWorkspacePermissionsChanged.event; } @@ -55,49 +55,55 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { } public configFontIsMonospace(): boolean { - this._createCharMeasureElementIfNecessary(); const fontSize = 15; const fontFamily = this.config.fontFamily || this._configurationService.getValue('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily; const i_rect = this._getBoundingRectFor('i', fontFamily, fontSize); const w_rect = this._getBoundingRectFor('w', fontFamily, fontSize); - const invalidBounds = !i_rect.width || !w_rect.width; - if (invalidBounds) { - // There is no reason to believe the font is not Monospace. + // Check for invalid bounds, there is no reason to believe the font is not monospace + if (!i_rect || !w_rect || !i_rect.width || !w_rect.width) { return true; } return i_rect.width === w_rect.width; } - private _createCharMeasureElementIfNecessary() { + private _createCharMeasureElementIfNecessary(): HTMLElement { + if (!this.panelContainer) { + throw new Error('Cannot measure element when terminal is not attached'); + } // Create charMeasureElement if it hasn't been created or if it was orphaned by its parent if (!this._charMeasureElement || !this._charMeasureElement.parentElement) { this._charMeasureElement = document.createElement('div'); this.panelContainer.appendChild(this._charMeasureElement); } + return this._charMeasureElement; } - private _getBoundingRectFor(char: string, fontFamily: string, fontSize: number): ClientRect | DOMRect { - const style = this._charMeasureElement.style; + private _getBoundingRectFor(char: string, fontFamily: string, fontSize: number): ClientRect | DOMRect | undefined { + let charMeasureElement: HTMLElement; + try { + charMeasureElement = this._createCharMeasureElementIfNecessary(); + } catch { + return undefined; + } + const style = charMeasureElement.style; style.display = 'inline-block'; style.fontFamily = fontFamily; style.fontSize = fontSize + 'px'; style.lineHeight = 'normal'; - this._charMeasureElement.innerText = char; - const rect = this._charMeasureElement.getBoundingClientRect(); + charMeasureElement.innerText = char; + const rect = charMeasureElement.getBoundingClientRect(); style.display = 'none'; return rect; } private _measureFont(fontFamily: string, fontSize: number, letterSpacing: number, lineHeight: number): ITerminalFont { - this._createCharMeasureElementIfNecessary(); - const rect = this._getBoundingRectFor('X', fontFamily, fontSize); // Bounding client rect was invalid, use last font measurement if available. - if (this._lastFontMeasurement && !rect.width && !rect.height) { + if (this._lastFontMeasurement && (!rect || !rect.width || !rect.height)) { return this._lastFontMeasurement; } @@ -106,8 +112,8 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { fontSize, letterSpacing, lineHeight, - charWidth: rect.width, - charHeight: Math.ceil(rect.height) + charWidth: rect && rect.width ? rect.width : 0, + charHeight: rect && rect.height ? Math.ceil(rect.height) : 0 }; return this._lastFontMeasurement; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts b/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts index 049f2455c6..bb414db506 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SimpleFindWidget } from 'vs/editor/contrib/find/simpleFindWidget'; +import { SimpleFindWidget } from 'vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { ITerminalService, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED } from 'vs/workbench/contrib/terminal/common/terminal'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -78,4 +78,14 @@ export class TerminalFindWidget extends SimpleFindWidget { protected onFindInputFocusTrackerBlur() { this._findInputFocused.reset(); } + + public findFirst() { + const instance = this._terminalService.getActiveInstance(); + if (instance) { + if (instance.hasSelection()) { + instance.clearSelection(); + } + instance.findPrevious(this.inputValue, { regex: this._getRegexValue(), wholeWord: this._getWholeWordValue(), caseSensitive: this._getCaseSensitiveValue() }); + } + } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 393bd2b17f..8d766d77f7 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -9,7 +9,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { debounce } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; -import * as lifecycle from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { TabFocus } from 'vs/editor/common/config/commonEditorConfig'; import * as nls from 'vs/nls'; @@ -30,13 +30,13 @@ import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGR import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminalCommands'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalLinkHandler } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler'; -import { TerminalCommandTracker } from 'vs/workbench/contrib/terminal/browser/terminalCommandTracker'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager'; import { Terminal as XTermTerminal, IBuffer, ITerminalAddon } from 'xterm'; import { SearchAddon, ISearchOptions } from 'xterm-addon-search'; +import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon'; import { NavigationModeAddon } from 'vs/workbench/contrib/terminal/browser/addons/navigationModeAddon'; // How long in milliseconds should an average frame take to render for a notification to appear @@ -165,15 +165,15 @@ interface IGridDimensions { rows: number; } -export class TerminalInstance implements ITerminalInstance { +export class TerminalInstance extends Disposable implements ITerminalInstance { private static readonly EOL_REGEX = /\r?\n/g; private static _lastKnownCanvasDimensions: ICanvasDimensions | undefined; private static _lastKnownGridDimensions: IGridDimensions | undefined; private static _idCounter = 1; - private _processManager: ITerminalProcessManager | undefined; - private _pressAnyKeyToCloseListener: lifecycle.IDisposable | undefined; + private _processManager!: ITerminalProcessManager; + private _pressAnyKeyToCloseListener: IDisposable | undefined; private _id: number; private _isExiting: boolean; @@ -181,27 +181,26 @@ export class TerminalInstance implements ITerminalInstance { private _isVisible: boolean; private _isDisposed: boolean; private _skipTerminalCommands: string[]; - private _title: string; - private _wrapperElement: HTMLDivElement; - private _xterm: XTermTerminal; + private _title: string = ''; + private _wrapperElement: (HTMLElement & { xterm?: XTermTerminal }) | undefined; + private _xterm: XTermTerminal | undefined; private _xtermSearch: SearchAddon | undefined; - private _xtermElement: HTMLDivElement; + private _xtermElement: HTMLDivElement | undefined; private _terminalHasTextContextKey: IContextKey; private _terminalA11yTreeFocusContextKey: IContextKey; - private _cols: number; - private _rows: number; + private _cols: number = 0; + private _rows: number = 0; private _dimensionsOverride: ITerminalDimensions | undefined; private _windowsShellHelper: IWindowsShellHelper | undefined; - private _xtermReadyPromise: Promise; + private _xtermReadyPromise: Promise; private _titleReadyPromise: Promise; - private _titleReadyComplete: (title: string) => any; + private _titleReadyComplete: ((title: string) => any) | undefined; - private readonly _disposables = new lifecycle.DisposableStore(); - private _messageTitleDisposable: lifecycle.IDisposable | undefined; + private _messageTitleDisposable: IDisposable | undefined; - private _widgetManager: TerminalWidgetManager; - private _linkHandler: TerminalLinkHandler; - private _commandTracker: TerminalCommandTracker; + private _widgetManager: TerminalWidgetManager | undefined; + private _linkHandler: TerminalLinkHandler | undefined; + private _commandTrackerAddon: CommandTrackerAddon | undefined; private _navigationModeAddon: INavigationMode & ITerminalAddon | undefined; public disableLayout: boolean; @@ -221,15 +220,15 @@ export class TerminalInstance implements ITerminalInstance { public get maxCols(): number { return this._cols; } public get maxRows(): number { return this._rows; } // TODO: Ideally processId would be merged into processReady - public get processId(): number | undefined { return this._processManager ? this._processManager.shellProcessId : undefined; } + public get processId(): number | undefined { return this._processManager.shellProcessId; } // TODO: How does this work with detached processes? // TODO: Should this be an event as it can fire twice? - public get processReady(): Promise { return this._processManager ? this._processManager.ptyProcessReady : Promise.resolve(undefined); } + public get processReady(): Promise { return this._processManager.ptyProcessReady; } public get title(): string { return this._title; } public get hadFocusOnExit(): boolean { return this._hadFocusOnExit; } public get isTitleSetByProcess(): boolean { return !!this._messageTitleDisposable; } public get shellLaunchConfig(): IShellLaunchConfig { return this._shellLaunchConfig; } - public get commandTracker(): TerminalCommandTracker { return this._commandTracker; } + public get commandTracker(): CommandTrackerAddon | undefined { return this._commandTrackerAddon; } public get navigationMode(): INavigationMode | undefined { return this._navigationModeAddon; } private readonly _onExit = new Emitter(); @@ -246,8 +245,6 @@ export class TerminalInstance implements ITerminalInstance { public get onData(): Event { return this._onData.event; } private readonly _onLineData = new Emitter(); public get onLineData(): Event { return this._onLineData.event; } - private readonly _onRendererInput = new Emitter(); - public get onRendererInput(): Event { return this._onRendererInput.event; } private readonly _onRequestExtHostProcess = new Emitter(); public get onRequestExtHostProcess(): Event { return this._onRequestExtHostProcess.event; } private readonly _onDimensionsChanged = new Emitter(); @@ -275,6 +272,8 @@ export class TerminalInstance implements ITerminalInstance { @IStorageService private readonly _storageService: IStorageService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService ) { + super(); + this._skipTerminalCommands = []; this._isExiting = false; this._hadFocusOnExit = false; @@ -293,11 +292,7 @@ export class TerminalInstance implements ITerminalInstance { this._logService.trace(`terminalInstance#ctor (id: ${this.id})`, this._shellLaunchConfig); this._initDimensions(); - if (!this.shellLaunchConfig.isRendererOnly) { - this._createProcess(); - } else { - this.setTitle(this._shellLaunchConfig.name, false); - } + this._createProcess(); this._xtermReadyPromise = this._createXterm(); this._xtermReadyPromise.then(() => { @@ -321,8 +316,8 @@ export class TerminalInstance implements ITerminalInstance { })); } - public addDisposable(disposable: lifecycle.IDisposable): void { - this._disposables.add(disposable); + public addDisposable(disposable: IDisposable): void { + this._register(disposable); } private _initDimensions(): void { @@ -455,11 +450,11 @@ export class TerminalInstance implements ITerminalInstance { /** * Create xterm.js instance and attach data listeners. */ - protected async _createXterm(): Promise { + protected async _createXterm(): Promise { const Terminal = await this._getXtermConstructor(); const font = this._configHelper.getFont(undefined, true); const config = this._configHelper.config; - this._xterm = new Terminal({ + const xterm = new Terminal({ scrollback: config.scrollback, theme: this._getXtermTheme(), drawBoldTextInBrightColors: config.drawBoldTextInBrightColors, @@ -476,53 +471,48 @@ export class TerminalInstance implements ITerminalInstance { // TODO: Guess whether to use canvas or dom better rendererType: config.rendererType === 'auto' ? 'canvas' : config.rendererType }); + this._xterm = xterm; this.updateAccessibilitySupport(); this._terminalInstanceService.getXtermSearchConstructor().then(Addon => { this._xtermSearch = new Addon(); - this._xterm.loadAddon(this._xtermSearch); + xterm.loadAddon(this._xtermSearch); }); if (this._shellLaunchConfig.initialText) { this._xterm.writeln(this._shellLaunchConfig.initialText); } this._xterm.onLineFeed(() => this._onLineFeed()); this._xterm.onKey(e => this._onKey(e.key, e.domEvent)); + this._xterm.onSelectionChange(async () => this._onSelectionChange()); - if (this._processManager) { - this._processManager.onProcessData(data => this._onProcessData(data)); - this._xterm.onData(data => this._processManager!.write(data)); - // TODO: How does the cwd work on detached processes? - this.processReady.then(async () => { - this._linkHandler.processCwd = await this._processManager!.getInitialCwd(); - }); - // Init winpty compat and link handler after process creation as they rely on the - // underlying process OS - this._processManager.onProcessReady(() => { - if (!this._processManager) { - return; - } - if (this._processManager.os === platform.OperatingSystem.Windows) { - this._xterm.setOption('windowsMode', true); - // Force line data to be sent when the cursor is moved, the main purpose for - // this is because ConPTY will often not do a line feed but instead move the - // cursor, in which case we still want to send the current line's data to tasks. - this._xterm.addCsiHandler('H', () => { - this._onCursorMove(); - return false; - }); - } - this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, this._processManager); - }); - } else if (this.shellLaunchConfig.isRendererOnly) { - this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, undefined); - } + this._processManager.onProcessData(data => this._onProcessData(data)); + this._xterm.onData(data => this._processManager.write(data)); + // TODO: How does the cwd work on detached processes? + this.processReady.then(async () => { + if (this._linkHandler) { + this._linkHandler.processCwd = await this._processManager.getInitialCwd(); + } + }); + // Init winpty compat and link handler after process creation as they rely on the + // underlying process OS + this._processManager.onProcessReady(() => { + if (this._processManager.os === platform.OperatingSystem.Windows) { + xterm.setOption('windowsMode', true); + // Force line data to be sent when the cursor is moved, the main purpose for + // this is because ConPTY will often not do a line feed but instead move the + // cursor, in which case we still want to send the current line's data to tasks. + xterm.addCsiHandler('H', () => { + this._onCursorMove(); + return false; + }); + } + this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, this._processManager, this._configHelper); + }); - // Register listener to trigger the onInput ext API if the terminal is a renderer only - if (this._shellLaunchConfig.isRendererOnly) { - this._xterm.onData(data => this._sendRendererInput(data)); - } + this._commandTrackerAddon = new CommandTrackerAddon(); + this._xterm.loadAddon(this._commandTrackerAddon); + this._register(this._themeService.onThemeChange(theme => this._updateTheme(xterm, theme))); - this._commandTracker = new TerminalCommandTracker(this._xterm); - this._disposables.add(this._themeService.onThemeChange(theme => this._updateTheme(theme))); + return xterm; } private _isScreenReaderOptimized(): boolean { @@ -562,7 +552,7 @@ export class TerminalInstance implements ITerminalInstance { } public _attachToElement(container: HTMLElement): void { - this._xtermReadyPromise.then(() => { + this._xtermReadyPromise.then(xterm => { if (this._wrapperElement) { throw new Error('The terminal instance has already been attached to a container'); } @@ -573,11 +563,11 @@ export class TerminalInstance implements ITerminalInstance { this._xtermElement = document.createElement('div'); // Attach the xterm object to the DOM, exposing it to the smoke tests - (this._wrapperElement).xterm = this._xterm; + this._wrapperElement.xterm = this._xterm; - this._xterm.open(this._xtermElement); - this._xterm.textarea.addEventListener('focus', () => this._onFocus.fire(this)); - this._xterm.attachCustomKeyEventHandler((event: KeyboardEvent): boolean => { + xterm.open(this._xtermElement); + xterm.textarea.addEventListener('focus', () => this._onFocus.fire(this)); + xterm.attachCustomKeyEventHandler((event: KeyboardEvent): boolean => { // Disable all input if the terminal is exiting if (this._isExiting) { return false; @@ -604,7 +594,7 @@ export class TerminalInstance implements ITerminalInstance { return true; }); - this._disposables.add(dom.addDisposableListener(this._xterm.element, 'mousedown', () => { + this._register(dom.addDisposableListener(xterm.element, 'mousedown', () => { // We need to listen to the mouseup event on the document since the user may release // the mouse button anywhere outside of _xterm.element. const listener = dom.addDisposableListener(document, 'mouseup', () => { @@ -616,17 +606,17 @@ export class TerminalInstance implements ITerminalInstance { })); // xterm.js currently drops selection on keyup as we need to handle this case. - this._disposables.add(dom.addDisposableListener(this._xterm.element, 'keyup', () => { + this._register(dom.addDisposableListener(xterm.element, 'keyup', () => { // Wait until keyup has propagated through the DOM before evaluating // the new selection state. setTimeout(() => this._refreshSelectionContextKey(), 0); })); - const xtermHelper: HTMLElement = this._xterm.element.querySelector('.xterm-helpers'); + const xtermHelper: HTMLElement = xterm.element.querySelector('.xterm-helpers'); const focusTrap: HTMLElement = document.createElement('div'); focusTrap.setAttribute('tabindex', '0'); dom.addClass(focusTrap, 'focus-trap'); - this._disposables.add(dom.addDisposableListener(focusTrap, 'focus', () => { + this._register(dom.addDisposableListener(focusTrap, 'focus', () => { let currentElement = focusTrap; while (!dom.hasClass(currentElement, 'part')) { currentElement = currentElement.parentElement!; @@ -634,20 +624,20 @@ export class TerminalInstance implements ITerminalInstance { const hidePanelElement = currentElement.querySelector('.hide-panel-action'); hidePanelElement.focus(); })); - xtermHelper.insertBefore(focusTrap, this._xterm.textarea); + xtermHelper.insertBefore(focusTrap, xterm.textarea); - this._disposables.add(dom.addDisposableListener(this._xterm.textarea, 'focus', () => { + this._register(dom.addDisposableListener(xterm.textarea, 'focus', () => { this._terminalFocusContextKey.set(true); this._onFocused.fire(this); })); - this._disposables.add(dom.addDisposableListener(this._xterm.textarea, 'blur', () => { + this._register(dom.addDisposableListener(xterm.textarea, 'blur', () => { this._terminalFocusContextKey.reset(); this._refreshSelectionContextKey(); })); - this._disposables.add(dom.addDisposableListener(this._xterm.element, 'focus', () => { + this._register(dom.addDisposableListener(xterm.element, 'focus', () => { this._terminalFocusContextKey.set(true); })); - this._disposables.add(dom.addDisposableListener(this._xterm.element, 'blur', () => { + this._register(dom.addDisposableListener(xterm.element, 'blur', () => { this._terminalFocusContextKey.reset(); this._refreshSelectionContextKey(); })); @@ -655,13 +645,13 @@ export class TerminalInstance implements ITerminalInstance { this._wrapperElement.appendChild(this._xtermElement); this._container.appendChild(this._wrapperElement); - if (this._processManager) { - this._widgetManager = new TerminalWidgetManager(this._wrapperElement); - this._processManager.onProcessReady(() => this._linkHandler.setWidgetManager(this._widgetManager)); - } else if (this._shellLaunchConfig.isRendererOnly) { - this._widgetManager = new TerminalWidgetManager(this._wrapperElement); - this._linkHandler.setWidgetManager(this._widgetManager); - } + const widgetManager = new TerminalWidgetManager(this._wrapperElement); + this._widgetManager = widgetManager; + this._processManager.onProcessReady(() => { + if (this._linkHandler) { + this._linkHandler.setWidgetManager(widgetManager); + } + }); const computedStyle = window.getComputedStyle(this._container); const width = parseInt(computedStyle.getPropertyValue('width').replace('px', ''), 10); @@ -672,8 +662,8 @@ export class TerminalInstance implements ITerminalInstance { // If IShellLaunchConfig.waitOnExit was true and the process finished before the terminal // panel was initialized. - if (this._xterm.getOption('disableStdin')) { - this._attachPressAnyKeyToCloseListener(); + if (xterm.getOption('disableStdin')) { + this._attachPressAnyKeyToCloseListener(xterm); } const neverMeasureRenderTime = this._storageService.getBoolean(NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, StorageScope.GLOBAL, false); @@ -683,9 +673,10 @@ export class TerminalInstance implements ITerminalInstance { }); } - private _measureRenderTime(): void { + private async _measureRenderTime(): Promise { + const xterm = await this._xtermReadyPromise; const frameTimes: number[] = []; - const textRenderLayer = this._xterm._core._renderService._renderer._renderLayers[0]; + const textRenderLayer = xterm._core._renderService._renderer._renderLayers[0]; const originalOnGridChanged = textRenderLayer.onGridChanged; const evaluateCanvasRenderer = () => { @@ -730,34 +721,41 @@ export class TerminalInstance implements ITerminalInstance { } public registerLinkMatcher(regex: RegExp, handler: (url: string) => void, matchIndex?: number, validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void): number { - return this._linkHandler.registerCustomLinkHandler(regex, handler, matchIndex, validationCallback); + return this._linkHandler!.registerCustomLinkHandler(regex, handler, matchIndex, validationCallback); } public deregisterLinkMatcher(linkMatcherId: number): void { - this._xterm.deregisterLinkMatcher(linkMatcherId); + this._xtermReadyPromise.then(xterm => xterm.deregisterLinkMatcher(linkMatcherId)); } public hasSelection(): boolean { - return this._xterm && this._xterm.hasSelection(); + return this._xterm ? this._xterm.hasSelection() : false; } public async copySelection(): Promise { + const xterm = await this._xtermReadyPromise; if (this.hasSelection()) { - await this._clipboardService.writeText(this._xterm.getSelection()); + await this._clipboardService.writeText(xterm.getSelection()); } else { this._notificationService.warn(nls.localize('terminal.integrated.copySelection.noSelection', 'The terminal has no selection to copy')); } } public get selection(): string | undefined { - return this.hasSelection() ? this._xterm.getSelection() : undefined; + return this._xterm && this.hasSelection() ? this._xterm.getSelection() : undefined; } public clearSelection(): void { + if (!this._xterm) { + return; + } this._xterm.clearSelection(); } public selectAll(): void { + if (!this._xterm) { + return; + } // Focus here to ensure the terminal context key is set this._xterm.focus(); this._xterm.selectAll(); @@ -778,6 +776,9 @@ export class TerminalInstance implements ITerminalInstance { } public notifyFindWidgetFocusChanged(isFocused: boolean): void { + if (!this._xterm) { + return; + } const terminalFocused = !isFocused && (document.activeElement === this._xterm.textarea || document.activeElement === this._xterm.element); this._terminalFocusContextKey.set(terminalFocused); } @@ -785,18 +786,18 @@ export class TerminalInstance implements ITerminalInstance { public dispose(immediate?: boolean): void { this._logService.trace(`terminalInstance#dispose (id: ${this.id})`); - lifecycle.dispose(this._windowsShellHelper); + dispose(this._windowsShellHelper); this._windowsShellHelper = undefined; - this._linkHandler = lifecycle.dispose(this._linkHandler); - this._commandTracker = lifecycle.dispose(this._commandTracker); - this._widgetManager = lifecycle.dispose(this._widgetManager); + this._linkHandler = dispose(this._linkHandler); + this._commandTrackerAddon = dispose(this._commandTrackerAddon); + this._widgetManager = dispose(this._widgetManager); if (this._xterm && this._xterm.element) { this._hadFocusOnExit = dom.hasClass(this._xterm.element, 'focus'); } if (this._wrapperElement) { - if ((this._wrapperElement).xterm) { - (this._wrapperElement).xterm = null; + if (this._wrapperElement.xterm) { + this._wrapperElement.xterm = undefined; } if (this._wrapperElement.parentElement) { this._container.removeChild(this._wrapperElement); @@ -813,39 +814,29 @@ export class TerminalInstance implements ITerminalInstance { this._pressAnyKeyToCloseListener = undefined; } - if (this._processManager) { - this._processManager.dispose(immediate); - } else { - // In cases where there is no associated process (for example executing an extension callback task) - // consumers still expect on onExit event to be fired. An example of this is terminating the extension callback - // task. - this._onExit.fire(0); - } + this._processManager.dispose(immediate); + // Process manager dispose/shutdown doesn't fire process exit, trigger with undefined if it + // hasn't happened yet + this._onProcessExit(undefined); if (!this._isDisposed) { this._isDisposed = true; this._onDisposed.fire(this); } - this._disposables.dispose(); - } - - public rendererExit(exitCode: number): void { - // The use of this API is for cases where there is no backing process behind a terminal - // instance (e.g. a custom execution task). - if (!this.shellLaunchConfig.isRendererOnly) { - throw new Error('rendererExit is only expected to be called on a renderer only terminal'); - } - - return this._onProcessExit(exitCode); + super.dispose(); } public forceRedraw(): void { + if (!this._xterm) { + return; + } if (this._configHelper.config.experimentalRefreshOnResume) { if (this._xterm.getOption('rendererType') !== 'dom') { this._xterm.setOption('rendererType', 'dom'); // Do this asynchronously to clear our the texture atlas as all terminals will not // be using canvas - setTimeout(() => this._xterm.setOption('rendererType', 'canvas'), 0); + const xterm = this._xterm; + setTimeout(() => xterm.setOption('rendererType', 'canvas'), 0); } } this._xterm.refresh(0, this._xterm.rows - 1); @@ -870,6 +861,9 @@ export class TerminalInstance implements ITerminalInstance { } public async paste(): Promise { + if (!this._xterm) { + return; + } this.focus(); this._xterm._core._coreService.triggerDataEvent(await this._clipboardService.readText(), true); } @@ -880,10 +874,6 @@ export class TerminalInstance implements ITerminalInstance { return; } this._xterm.write(text); - if (this._shellLaunchConfig.isRendererOnly) { - // Fire onData API in the extension host - this._onData.fire(text); - } }); } @@ -894,17 +884,8 @@ export class TerminalInstance implements ITerminalInstance { text += '\r'; } - if (this._shellLaunchConfig.isRendererOnly) { - // If the terminal is a renderer only, fire the onInput ext API - this._sendRendererInput(text); - } else { - // If the terminal has a process, send it to the process - if (this._processManager) { - this._processManager.ptyProcessReady.then(() => { - this._processManager!.write(text); - }); - } - } + // Send it to the process + this._processManager.ptyProcessReady.then(() => this._processManager.write(text)); } public setVisible(visible: boolean): void { @@ -936,31 +917,45 @@ export class TerminalInstance implements ITerminalInstance { } public scrollDownLine(): void { - this._xterm.scrollLines(1); + if (this._xterm) { + this._xterm.scrollLines(1); + } } public scrollDownPage(): void { - this._xterm.scrollPages(1); + if (this._xterm) { + this._xterm.scrollPages(1); + } } public scrollToBottom(): void { - this._xterm.scrollToBottom(); + if (this._xterm) { + this._xterm.scrollToBottom(); + } } public scrollUpLine(): void { - this._xterm.scrollLines(-1); + if (this._xterm) { + this._xterm.scrollLines(-1); + } } public scrollUpPage(): void { - this._xterm.scrollPages(-1); + if (this._xterm) { + this._xterm.scrollPages(-1); + } } public scrollToTop(): void { - this._xterm.scrollToTop(); + if (this._xterm) { + this._xterm.scrollToTop(); + } } public clear(): void { - this._xterm.clear(); + if (this._xterm) { + this._xterm.clear(); + } } private _refreshSelectionContextKey() { @@ -987,12 +982,12 @@ export class TerminalInstance implements ITerminalInstance { if (platform.isWindows) { this._processManager.ptyProcessReady.then(() => { - if (this._processManager!.remoteAuthority) { + if (this._processManager.remoteAuthority) { return; } - this._xtermReadyPromise.then(() => { - if (!this._isDisposed) { - this._windowsShellHelper = this._terminalInstanceService.createWindowsShellHelper(this._processManager!.shellProcessId, this, this._xterm); + this._xtermReadyPromise.then(xterm => { + if (!this._isDisposed && this._processManager && this._processManager.shellProcessId) { + this._windowsShellHelper = this._terminalInstanceService.createWindowsShellHelper(this._processManager.shellProcessId, this, xterm); } }); }); @@ -1001,7 +996,7 @@ export class TerminalInstance implements ITerminalInstance { // Create the process asynchronously to allow the terminal's container // to be created so dimensions are accurate setTimeout(() => { - this._processManager!.createProcess(this._shellLaunchConfig, this._cols, this._rows, this._isScreenReaderOptimized()); + this._processManager.createProcess(this._shellLaunchConfig, this._cols, this._rows, this._isScreenReaderOptimized()); }, 0); } @@ -1021,13 +1016,13 @@ export class TerminalInstance implements ITerminalInstance { * through user action. */ private _onProcessExit(exitCode?: number): void { - this._logService.debug(`Terminal process exit (id: ${this.id}) with code ${exitCode}`); - // Prevent dispose functions being triggered multiple times if (this._isExiting) { return; } + this._logService.debug(`Terminal process exit (id: ${this.id}) with code ${exitCode}`); + this._isExiting = true; let exitCodeMessage: string | undefined; @@ -1039,7 +1034,7 @@ export class TerminalInstance implements ITerminalInstance { exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidPathDirectory', 'The terminal shell path "{0}" is a directory', this._shellLaunchConfig.executable); } else if (exitCode === SHELL_CWD_INVALID_EXIT_CODE && this._shellLaunchConfig.cwd) { exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidCWD', 'The terminal shell CWD "{0}" does not exist', this._shellLaunchConfig.cwd.toString()); - } else if (this._processManager && this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) { + } else if (this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) { let args = ''; if (typeof this._shellLaunchConfig.args === 'string') { args = ` ${this._shellLaunchConfig.args}`; @@ -1061,29 +1056,31 @@ export class TerminalInstance implements ITerminalInstance { } } - this._logService.debug(`Terminal process exit (id: ${this.id})${this._processManager ? ' state ' + this._processManager.processState : ''}`); + this._logService.debug(`Terminal process exit (id: ${this.id}) state ${this._processManager.processState}`); // Only trigger wait on exit when the exit was *not* triggered by the // user (via the `workbench.action.terminal.kill` command). - if (this._shellLaunchConfig.waitOnExit && (!this._processManager || this._processManager.processState !== ProcessState.KILLED_BY_USER)) { - if (exitCodeMessage) { - this._xterm.writeln(exitCodeMessage); - } - if (typeof this._shellLaunchConfig.waitOnExit === 'string') { - let message = this._shellLaunchConfig.waitOnExit; - // Bold the message and add an extra new line to make it stand out from the rest of the output - message = `\r\n\x1b[1m${message}\x1b[0m`; - this._xterm.writeln(message); - } - // Disable all input if the terminal is exiting and listen for next keypress - this._xterm.setOption('disableStdin', true); - if (this._xterm.textarea) { - this._attachPressAnyKeyToCloseListener(); - } + if (this._shellLaunchConfig.waitOnExit && this._processManager.processState !== ProcessState.KILLED_BY_USER) { + this._xtermReadyPromise.then(xterm => { + if (exitCodeMessage) { + xterm.writeln(exitCodeMessage); + } + if (typeof this._shellLaunchConfig.waitOnExit === 'string') { + let message = this._shellLaunchConfig.waitOnExit; + // Bold the message and add an extra new line to make it stand out from the rest of the output + message = `\r\n\x1b[1m${message}\x1b[0m`; + xterm.writeln(message); + } + // Disable all input if the terminal is exiting and listen for next keypress + xterm.setOption('disableStdin', true); + if (xterm.textarea) { + this._attachPressAnyKeyToCloseListener(xterm); + } + }); } else { this.dispose(); if (exitCodeMessage) { - if (this._processManager && this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) { + if (this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) { this._notificationService.error(exitCodeMessage); } else { if (this._configHelper.config.showExitAlert) { @@ -1098,9 +1095,9 @@ export class TerminalInstance implements ITerminalInstance { this._onExit.fire(exitCode || 0); } - private _attachPressAnyKeyToCloseListener() { + private _attachPressAnyKeyToCloseListener(xterm: XTermTerminal) { if (!this._pressAnyKeyToCloseListener) { - this._pressAnyKeyToCloseListener = dom.addDisposableListener(this._xterm.textarea, 'keypress', (event: KeyboardEvent) => { + this._pressAnyKeyToCloseListener = dom.addDisposableListener(xterm.textarea, 'keypress', (event: KeyboardEvent) => { if (this._pressAnyKeyToCloseListener) { this._pressAnyKeyToCloseListener.dispose(); this._pressAnyKeyToCloseListener = undefined; @@ -1119,59 +1116,47 @@ export class TerminalInstance implements ITerminalInstance { } // Kill and clear up the process, making the process manager ready for a new process - if (this._processManager) { - this._processManager.dispose(); - this._processManager = undefined; + this._processManager.dispose(); + + if (this._xterm) { + // Ensure new processes' output starts at start of new line + this._xterm.write('\n\x1b[G'); + + // Print initialText if specified + if (shell.initialText) { + this._xterm.writeln(shell.initialText); + } + + // Clean up waitOnExit state + if (this._isExiting && this._shellLaunchConfig.waitOnExit) { + this._xterm.setOption('disableStdin', false); + this._isExiting = false; + } } - // Ensure new processes' output starts at start of new line - this._xterm.write('\n\x1b[G'); - - // Print initialText if specified - if (shell.initialText) { - this._xterm.writeln(shell.initialText); - } - - const oldTitle = this._title; - // Clean up waitOnExit state - if (this._isExiting && this._shellLaunchConfig.waitOnExit) { - this._xterm.setOption('disableStdin', false); - this._isExiting = false; - } + // HACK: Force initialText to be non-falsy for reused terminals such that the + // conptyInheritCursor flag is passed to the node-pty, this flag can cause a Window to hang + // in Windows 10 1903 so we only want to use it when something is definitely written to the + // terminal. + shell.initialText = ' '; // Set the new shell launch config this._shellLaunchConfig = shell; // Must be done before calling _createProcess() // Launch the process unless this is only a renderer. // In the renderer only cases, we still need to set the title correctly. - if (!this._shellLaunchConfig.isRendererOnly) { - this._createProcess(); - } else if (this._shellLaunchConfig.name) { - this.setTitle(this._shellLaunchConfig.name, false); - } + const oldTitle = this._title; + this._createProcess(); if (oldTitle !== this._title) { this.setTitle(this._title, true); } - if (this._processManager) { - // The "!" operator is required here because _processManager is set to undefiend earlier - // and TS does not know that createProcess sets it. - this._processManager!.onProcessData(data => this._onProcessData(data)); - } - } - - private _sendRendererInput(input: string): void { - if (this._processManager) { - throw new Error('onRendererInput attempted to be used on a regular terminal'); - } - - // For terminal renderers onData fires on keystrokes and when sendText is called. - this._onRendererInput.fire(input); + this._processManager.onProcessData(data => this._onProcessData(data)); } private _onLineFeed(): void { - const buffer = this._xterm.buffer; + const buffer = this._xterm!.buffer; const newLine = buffer.getLine(buffer.baseY + buffer.cursorY); if (newLine && !newLine.isWrapped) { this._sendLineData(buffer, buffer.baseY + buffer.cursorY - 1); @@ -1179,7 +1164,7 @@ export class TerminalInstance implements ITerminalInstance { } private _onCursorMove(): void { - const buffer = this._xterm.buffer; + const buffer = this._xterm!.buffer; this._sendLineData(buffer, buffer.baseY + buffer.cursorY); } @@ -1207,11 +1192,19 @@ export class TerminalInstance implements ITerminalInstance { } } + private async _onSelectionChange(): Promise { + if (this._configurationService.getValue('terminal.integrated.copyOnSelection')) { + if (this.hasSelection()) { + await this.copySelection(); + } + } + } + @debounce(2000) private async _updateProcessCwd(): Promise { // reset cwd if it has changed, so file based url paths can be resolved const cwd = await this.getCwd(); - if (cwd) { + if (cwd && this._linkHandler) { this._linkHandler.processCwd = cwd; } return cwd; @@ -1234,14 +1227,14 @@ export class TerminalInstance implements ITerminalInstance { const isEnabled = this._isScreenReaderOptimized(); if (isEnabled) { this._navigationModeAddon = new NavigationModeAddon(this._terminalA11yTreeFocusContextKey); - this._xterm.loadAddon(this._navigationModeAddon); + this._xterm!.loadAddon(this._navigationModeAddon); } else { if (this._navigationModeAddon) { this._navigationModeAddon.dispose(); this._navigationModeAddon = undefined; } } - this._xterm.setOption('screenReaderMode', isEnabled); + this._xterm!.setOption('screenReaderMode', isEnabled); } private _setCursorBlink(blink: boolean): void { @@ -1352,9 +1345,7 @@ export class TerminalInstance implements ITerminalInstance { } } - if (this._processManager) { - this._processManager.ptyProcessReady.then(() => this._processManager!.setDimensions(cols, rows)); - } + this._processManager.ptyProcessReady.then(() => this._processManager.setDimensions(cols, rows)); } public setTitle(title: string | undefined, eventFromProcess: boolean): void { @@ -1370,19 +1361,17 @@ export class TerminalInstance implements ITerminalInstance { } else { // If the title has not been set by the API or the rename command, unregister the handler that // automatically updates the terminal name - if (this._messageTitleDisposable) { - lifecycle.dispose(this._messageTitleDisposable); - lifecycle.dispose(this._windowsShellHelper); - this._messageTitleDisposable = undefined; - this._windowsShellHelper = undefined; - } + dispose(this._messageTitleDisposable); + this._messageTitleDisposable = undefined; + dispose(this._windowsShellHelper); + this._windowsShellHelper = undefined; } const didTitleChange = title !== this._title; - const oldTitle = this._title; this._title = title; if (didTitleChange) { - if (!oldTitle) { + if (this._titleReadyComplete) { this._titleReadyComplete(title); + this._titleReadyComplete = undefined; } this._onTitleChanged.fire(this); } @@ -1440,25 +1429,21 @@ export class TerminalInstance implements ITerminalInstance { }; } - private _updateTheme(theme?: ITheme): void { - this._xterm.setOption('theme', this._getXtermTheme(theme)); + private _updateTheme(xterm: XTermTerminal, theme?: ITheme): void { + xterm.setOption('theme', this._getXtermTheme(theme)); } - public toggleEscapeSequenceLogging(): void { - this._xterm.setOption('logLevel', 'debug'); + public async toggleEscapeSequenceLogging(): Promise { + const xterm = await this._xtermReadyPromise; + const isDebug = xterm.getOption('logLevel') === 'debug'; + xterm.setOption('logLevel', isDebug ? 'info' : 'debug'); } public getInitialCwd(): Promise { - if (!this._processManager) { - return Promise.resolve(''); - } return this._processManager.getInitialCwd(); } public getCwd(): Promise { - if (!this._processManager) { - return Promise.resolve(''); - } return this._processManager.getCwd(); } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts index e1a86c04a7..709f99782d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts @@ -10,6 +10,7 @@ import { WebLinksAddon as XTermWebLinksAddon } from 'xterm-addon-web-links'; import { SearchAddon as XTermSearchAddon } from 'xterm-addon-search'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { Emitter, Event } from 'vs/base/common/event'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; let Terminal: typeof XTermTerminal; let WebLinksAddon: typeof XTermWebLinksAddon; @@ -52,11 +53,16 @@ export class TerminalInstanceService implements ITerminalInstanceService { throw new Error('Not implemented'); } - public getDefaultShellAndArgs(): Promise<{ shell: string, args: string[] | string | undefined }> { - return new Promise(r => this._onRequestDefaultShellAndArgs.fire((shell, args) => r({ shell, args }))); + public getDefaultShellAndArgs(useAutomationShell: boolean, ): Promise<{ shell: string, args: string[] | string | undefined }> { + return new Promise(r => this._onRequestDefaultShellAndArgs.fire({ + useAutomationShell, + callback: (shell, args) => r({ shell, args }) + })); } public async getMainProcessParentEnv(): Promise { return {}; } -} \ No newline at end of file +} + +registerSingleton(ITerminalInstanceService, TerminalInstanceService, true); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts index 6770bb336c..63c065ff2a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts @@ -5,15 +5,15 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import { dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/terminalWidgetManager'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ITerminalService, ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalProcessManager, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; import { ITextEditorSelection } from 'vs/platform/editor/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IFileService } from 'vs/platform/files/common/files'; -import { ILinkMatcherOptions } from 'xterm'; +import { Terminal, ILinkMatcherOptions } from 'xterm'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { posix, win32 } from 'vs/base/common/path'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -67,21 +67,20 @@ interface IPath { export class TerminalLinkHandler { private readonly _hoverDisposables = new DisposableStore(); - private _mouseMoveDisposable: IDisposable; - private _widgetManager: TerminalWidgetManager; - private _processCwd: string; + private _widgetManager: TerminalWidgetManager | undefined; + private _processCwd: string | undefined; private _gitDiffPreImagePattern: RegExp; private _gitDiffPostImagePattern: RegExp; private readonly _tooltipCallback: (event: MouseEvent, uri: string) => boolean | void; private readonly _leaveCallback: () => void; constructor( - private _xterm: any, + private _xterm: Terminal, private readonly _processManager: ITerminalProcessManager | undefined, + private readonly _configHelper: ITerminalConfigHelper, @IOpenerService private readonly _openerService: IOpenerService, @IEditorService private readonly _editorService: IEditorService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @ITerminalService private readonly _terminalService: ITerminalService, @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService, @IFileService private readonly _fileService: IFileService ) { @@ -94,7 +93,7 @@ export class TerminalLinkHandler { if (!this._widgetManager) { return; } - if (this._terminalService && this._terminalService.configHelper.config.rendererType === 'dom') { + if (this._configHelper.config.rendererType === 'dom') { const target = (e.target as HTMLElement); this._widgetManager.showMessage(target.offsetLeft, target.offsetTop, this._getLinkHoverString()); } else { @@ -183,10 +182,7 @@ export class TerminalLinkHandler { } public dispose(): void { - this._xterm = null; - this._hoverDisposables.dispose(); - this._mouseMoveDisposable = dispose(this._mouseMoveDisposable); } private _wrapLinkHandler(handler: (uri: string) => boolean | void): XtermLinkMatcherHandler { @@ -195,9 +191,6 @@ export class TerminalLinkHandler { event.preventDefault(); // Require correct modifier on click if (!this._isLinkActivationModifierDown(event)) { - // If the modifier is not pressed, the terminal should be - // focused if it's not already - this._terminalService.getActiveInstance()!.focus(true); return false; } return handler(uri); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalNativeService.ts b/src/vs/workbench/contrib/terminal/browser/terminalNativeService.ts index 4e92177357..bdaed4197a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalNativeService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalNativeService.ts @@ -6,6 +6,7 @@ import { IOpenFileRequest } from 'vs/platform/windows/common/windows'; import { ITerminalNativeService, LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal'; import { Emitter, Event } from 'vs/base/common/event'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class TerminalNativeService implements ITerminalNativeService { public _serviceBrand: any; @@ -30,4 +31,6 @@ export class TerminalNativeService implements ITerminalNativeService { public getWindowsBuildNumber(): number { throw new Error('Not implemented'); } -} \ No newline at end of file +} + +registerSingleton(ITerminalNativeService, TerminalNativeService, true); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts index 5a61370716..5a693e6ac8 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts @@ -29,14 +29,14 @@ const FIND_FOCUS_CLASS = 'find-focused'; export class TerminalPanel extends Panel { - private _actions: IAction[]; - private _copyContextMenuAction: IAction; - private _contextMenuActions: IAction[]; + private _actions: IAction[] | undefined; + private _copyContextMenuAction: IAction | undefined; + private _contextMenuActions: IAction[] | undefined; private _cancelContextMenu: boolean = false; - private _fontStyleElement: HTMLElement; - private _parentDomElement: HTMLElement; - private _terminalContainer: HTMLElement; - private _findWidget: TerminalFindWidget; + private _fontStyleElement: HTMLElement | undefined; + private _parentDomElement: HTMLElement | undefined; + private _terminalContainer: HTMLElement | undefined; + private _findWidget: TerminalFindWidget | undefined; constructor( @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -61,14 +61,13 @@ export class TerminalPanel extends Panel { dom.addClass(this._terminalContainer, 'terminal-outer-container'); this._findWidget = this._instantiationService.createInstance(TerminalFindWidget, this._terminalService.getFindState()); - this._findWidget.focusTracker.onDidFocus(() => this._terminalContainer.classList.add(FIND_FOCUS_CLASS)); - this._findWidget.focusTracker.onDidBlur(() => this._terminalContainer.classList.remove(FIND_FOCUS_CLASS)); + this._findWidget.focusTracker.onDidFocus(() => this._terminalContainer!.classList.add(FIND_FOCUS_CLASS)); this._parentDomElement.appendChild(this._fontStyleElement); this._parentDomElement.appendChild(this._terminalContainer); this._parentDomElement.appendChild(this._findWidget.getDomNode()); - this._attachEventListeners(); + this._attachEventListeners(this._parentDomElement, this._terminalContainer); this._terminalService.setContainers(this.getContainer(), this._terminalContainer); @@ -137,7 +136,7 @@ export class TerminalPanel extends Panel { } private _getContextMenuActions(): IAction[] { - if (!this._contextMenuActions) { + if (!this._contextMenuActions || !this._copyContextMenuAction) { this._copyContextMenuAction = this._instantiationService.createInstance(CopyTerminalSelectionAction, CopyTerminalSelectionAction.ID, CopyTerminalSelectionAction.SHORT_LABEL); this._contextMenuActions = [ this._instantiationService.createInstance(CreateNewTerminalAction, CreateNewTerminalAction.ID, CreateNewTerminalAction.SHORT_LABEL), @@ -179,31 +178,31 @@ export class TerminalPanel extends Panel { public focusFindWidget() { const activeInstance = this._terminalService.getActiveInstance(); if (activeInstance && activeInstance.hasSelection() && activeInstance.selection!.indexOf('\n') === -1) { - this._findWidget.reveal(activeInstance.selection); + this._findWidget!.reveal(activeInstance.selection); } else { - this._findWidget.reveal(); + this._findWidget!.reveal(); } } public hideFindWidget() { - this._findWidget.hide(); + this._findWidget!.hide(); } public showFindWidget() { const activeInstance = this._terminalService.getActiveInstance(); if (activeInstance && activeInstance.hasSelection() && activeInstance.selection!.indexOf('\n') === -1) { - this._findWidget.show(activeInstance.selection); + this._findWidget!.show(activeInstance.selection); } else { - this._findWidget.show(); + this._findWidget!.show(); } } public getFindWidget(): TerminalFindWidget { - return this._findWidget; + return this._findWidget!; } - private _attachEventListeners(): void { - this._register(dom.addDisposableListener(this._parentDomElement, 'mousedown', async (event: MouseEvent) => { + private _attachEventListeners(parentDomElement: HTMLElement, terminalContainer: HTMLElement): void { + this._register(dom.addDisposableListener(parentDomElement, 'mousedown', async (event: MouseEvent) => { if (this._terminalService.terminalInstances.length === 0) { return; } @@ -240,21 +239,7 @@ export class TerminalPanel extends Panel { } } })); - this._register(dom.addDisposableListener(this._parentDomElement, 'mouseup', async (event: MouseEvent) => { - if (this._configurationService.getValue('terminal.integrated.copyOnSelection')) { - if (this._terminalService.terminalInstances.length === 0) { - return; - } - - if (event.which === 1) { - const terminal = this._terminalService.getActiveInstance(); - if (terminal && terminal.hasSelection()) { - await terminal.copySelection(); - } - } - } - })); - this._register(dom.addDisposableListener(this._parentDomElement, 'contextmenu', (event: MouseEvent) => { + this._register(dom.addDisposableListener(parentDomElement, 'contextmenu', (event: MouseEvent) => { if (!this._cancelContextMenu) { const standardEvent = new StandardMouseEvent(event); const anchor: { x: number, y: number } = { x: standardEvent.posx, y: standardEvent.posy }; @@ -269,19 +254,19 @@ export class TerminalPanel extends Panel { this._cancelContextMenu = false; })); this._register(dom.addDisposableListener(document, 'keydown', (event: KeyboardEvent) => { - this._terminalContainer.classList.toggle('alt-active', !!event.altKey); + terminalContainer.classList.toggle('alt-active', !!event.altKey); })); this._register(dom.addDisposableListener(document, 'keyup', (event: KeyboardEvent) => { - this._terminalContainer.classList.toggle('alt-active', !!event.altKey); + terminalContainer.classList.toggle('alt-active', !!event.altKey); })); - this._register(dom.addDisposableListener(this._parentDomElement, 'keyup', (event: KeyboardEvent) => { + this._register(dom.addDisposableListener(parentDomElement, 'keyup', (event: KeyboardEvent) => { if (event.keyCode === 27) { // Keep terminal open on escape event.stopPropagation(); } })); - this._register(dom.addDisposableListener(this._parentDomElement, dom.EventType.DROP, async (e: DragEvent) => { - if (e.target === this._parentDomElement || dom.isAncestor(e.target as HTMLElement, this._parentDomElement)) { + this._register(dom.addDisposableListener(parentDomElement, dom.EventType.DROP, async (e: DragEvent) => { + if (e.target === this._parentDomElement || dom.isAncestor(e.target as HTMLElement, parentDomElement)) { if (!e.dataTransfer) { return; } @@ -315,11 +300,13 @@ export class TerminalPanel extends Panel { theme = this.themeService.getTheme(); } - this._findWidget.updateTheme(theme); + if (this._findWidget) { + this._findWidget.updateTheme(theme); + } } private _updateFont(): void { - if (this._terminalService.terminalInstances.length === 0) { + if (this._terminalService.terminalInstances.length === 0 || !this._parentDomElement) { return; } // TODO: Can we support ligatures? diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 7306dc750d..3ae3694719 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -20,6 +20,7 @@ import { IProductService } from 'vs/platform/product/common/product'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { Disposable } from 'vs/base/common/lifecycle'; /** The amount of time to consider terminal errors to be related to the launch */ const LAUNCHING_DURATION = 500; @@ -42,10 +43,10 @@ enum ProcessType { * - Pty Process: The pseudoterminal master process (or the winpty agent process) * - Shell Process: The pseudoterminal slave process (ie. the shell) */ -export class TerminalProcessManager implements ITerminalProcessManager { +export class TerminalProcessManager extends Disposable implements ITerminalProcessManager { public processState: ProcessState = ProcessState.UNINITIALIZED; public ptyProcessReady: Promise; - public shellProcessId: number; + public shellProcessId: number | undefined; public remoteAuthority: string | undefined; public os: platform.OperatingSystem | undefined; public userHome: string | undefined; @@ -54,23 +55,22 @@ export class TerminalProcessManager implements ITerminalProcessManager { private _processType: ProcessType = ProcessType.Process; private _preLaunchInputQueue: string[] = []; private _latency: number = -1; - private _latencyRequest: Promise; private _latencyLastMeasured: number = 0; - private _initialCwd: string; + private _initialCwd: string | undefined; - private readonly _onProcessReady = new Emitter(); + private readonly _onProcessReady = this._register(new Emitter()); public get onProcessReady(): Event { return this._onProcessReady.event; } - private readonly _onBeforeProcessData = new Emitter(); + private readonly _onBeforeProcessData = this._register(new Emitter()); public get onBeforeProcessData(): Event { return this._onBeforeProcessData.event; } - private readonly _onProcessData = new Emitter(); + private readonly _onProcessData = this._register(new Emitter()); public get onProcessData(): Event { return this._onProcessData.event; } - private readonly _onProcessTitle = new Emitter(); + private readonly _onProcessTitle = this._register(new Emitter()); public get onProcessTitle(): Event { return this._onProcessTitle.event; } - private readonly _onProcessExit = new Emitter(); + private readonly _onProcessExit = this._register(new Emitter()); public get onProcessExit(): Event { return this._onProcessExit.event; } - private readonly _onProcessOverrideDimensions = new Emitter(); + private readonly _onProcessOverrideDimensions = this._register(new Emitter()); public get onProcessOverrideDimensions(): Event { return this._onProcessOverrideDimensions.event; } - private readonly _onProcessOverrideShellLaunchConfig = new Emitter(); + private readonly _onProcessOverrideShellLaunchConfig = this._register(new Emitter()); public get onProcessResolvedShellLaunchConfig(): Event { return this._onProcessOverrideShellLaunchConfig.event; } constructor( @@ -87,6 +87,7 @@ export class TerminalProcessManager implements ITerminalProcessManager { @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService, @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService ) { + super(); this.ptyProcessReady = new Promise(c => { this.onProcessReady(() => { this._logService.debug(`Terminal process ready (shellProcessId: ${this.shellProcessId})`); @@ -105,6 +106,7 @@ export class TerminalProcessManager implements ITerminalProcessManager { this._process.shutdown(immediate); this._process = null; } + super.dispose(); } public async createProcess( @@ -193,7 +195,7 @@ export class TerminalProcessManager implements ITerminalProcessManager { const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux'); const lastActiveWorkspace = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) : null; if (!shellLaunchConfig.executable) { - const defaultConfig = await this._terminalInstanceService.getDefaultShellAndArgs(); + const defaultConfig = await this._terminalInstanceService.getDefaultShellAndArgs(false); shellLaunchConfig.executable = defaultConfig.shell; shellLaunchConfig.args = defaultConfig.args; } else { @@ -259,7 +261,7 @@ export class TerminalProcessManager implements ITerminalProcessManager { } public getInitialCwd(): Promise { - return Promise.resolve(this._initialCwd); + return Promise.resolve(this._initialCwd ? this._initialCwd : ''); } public getCwd(): Promise { @@ -275,8 +277,8 @@ export class TerminalProcessManager implements ITerminalProcessManager { return Promise.resolve(0); } if (this._latencyLastMeasured === 0 || this._latencyLastMeasured + LATENCY_MEASURING_INTERVAL < Date.now()) { - this._latencyRequest = this._process.getLatency(); - this._latency = await this._latencyRequest; + const latencyRequest = this._process.getLatency(); + this._latency = await latencyRequest; this._latencyLastMeasured = Date.now(); } return Promise.resolve(this._latency); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index ceba74cb1c..6b1668685c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -5,7 +5,7 @@ import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper, ITerminalNativeService } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalService as CommonTerminalService } from 'vs/workbench/contrib/terminal/common/terminalService'; -import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; @@ -49,18 +49,15 @@ export class TerminalService extends CommonTerminalService implements ITerminalS this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper, this.terminalNativeService.linuxDistro); } - public createInstance(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance { - const instance = this._instantiationService.createInstance(TerminalInstance, terminalFocusContextKey, configHelper, container, shellLaunchConfig); + public createInstance(container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance { + const instance = this._instantiationService.createInstance(TerminalInstance, this._terminalFocusContextKey, this._configHelper, container, shellLaunchConfig); this._onInstanceCreated.fire(instance); return instance; } public createTerminal(shell: IShellLaunchConfig = {}): ITerminalInstance { if (shell.hideFromUser) { - const instance = this.createInstance(this._terminalFocusContextKey, - this.configHelper, - undefined, - shell); + const instance = this.createInstance(undefined, shell); this._backgroundedTerminalInstances.push(instance); this._initInstanceListeners(instance); return instance; @@ -137,7 +134,7 @@ export class TerminalService extends CommonTerminalService implements ITerminalS public setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void { this._configHelper.panelContainer = panelContainer; this._terminalContainer = terminalContainer; - this._terminalTabs.forEach(tab => tab.attachToElement(this._terminalContainer)); + this._terminalTabs.forEach(tab => tab.attachToElement(terminalContainer)); } public hidePanel(): void { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTab.ts b/src/vs/workbench/contrib/terminal/browser/terminalTab.ts index 3ee8f4cf16..4b4d548b3e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTab.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTab.ts @@ -18,7 +18,7 @@ const TERMINAL_MIN_USEFUL_SIZE = 250; class SplitPaneContainer extends Disposable { private _height: number; private _width: number; - private _splitView: SplitView; + private _splitView!: SplitView; private readonly _splitViewDisposables = this._register(new DisposableStore()); private _children: SplitPane[] = []; @@ -177,7 +177,6 @@ class SplitPane implements IView { public maximumSize: number = Number.MAX_VALUE; public orientation: Orientation | undefined; - protected _size: number; private _onDidChange: Event = Event.None; public get onDidChange(): Event { return this._onDidChange; } @@ -195,15 +194,14 @@ class SplitPane implements IView { public layout(size: number): void { // Only layout when both sizes are known - this._size = size; - if (!this._size || !this.orthogonalSize) { + if (!size || !this.orthogonalSize) { return; } if (this.orientation === Orientation.VERTICAL) { - this.instance.layout({ width: this.orthogonalSize, height: this._size }); + this.instance.layout({ width: this.orthogonalSize, height: size }); } else { - this.instance.layout({ width: this._size, height: this.orthogonalSize }); + this.instance.layout({ width: size, height: this.orthogonalSize }); } } @@ -215,22 +213,22 @@ class SplitPane implements IView { export class TerminalTab extends Disposable implements ITerminalTab { private _terminalInstances: ITerminalInstance[] = []; private _splitPaneContainer: SplitPaneContainer | undefined; - private _tabElement: HTMLElement | null; + private _tabElement: HTMLElement | undefined; private _panelPosition: Position = Position.BOTTOM; private _activeInstanceIndex: number; public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; } - private readonly _onDisposed: Emitter = new Emitter(); + private readonly _onDisposed: Emitter = this._register(new Emitter()); public readonly onDisposed: Event = this._onDisposed.event; - private readonly _onInstancesChanged: Emitter = new Emitter(); + private readonly _onInstancesChanged: Emitter = this._register(new Emitter()); public readonly onInstancesChanged: Event = this._onInstancesChanged.event; constructor( terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, - private _container: HTMLElement, + private _container: HTMLElement | undefined, shellLaunchConfigOrInstance: IShellLaunchConfig | ITerminalInstance, @ITerminalService private readonly _terminalService: ITerminalService, @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, @@ -242,11 +240,7 @@ export class TerminalTab extends Disposable implements ITerminalTab { if ('id' in shellLaunchConfigOrInstance) { instance = shellLaunchConfigOrInstance; } else { - instance = this._terminalService.createInstance( - terminalFocusContextKey, - configHelper, - undefined, - shellLaunchConfigOrInstance); + instance = this._terminalService.createInstance(undefined, shellLaunchConfigOrInstance); } this._terminalInstances.push(instance); this._initInstanceListeners(instance); @@ -259,9 +253,9 @@ export class TerminalTab extends Disposable implements ITerminalTab { public dispose(): void { super.dispose(); - if (this._tabElement) { + if (this._container && this._tabElement) { this._container.removeChild(this._tabElement); - this._tabElement = null; + this._tabElement = undefined; } this._terminalInstances = []; this._onInstancesChanged.fire(); @@ -380,15 +374,14 @@ export class TerminalTab extends Disposable implements ITerminalTab { configHelper: ITerminalConfigHelper, shellLaunchConfig: IShellLaunchConfig ): ITerminalInstance | undefined { + if (!this._container) { + throw new Error('Cannot split terminal that has not been attached'); + } const newTerminalSize = ((this._panelPosition === Position.BOTTOM ? this._container.clientWidth : this._container.clientHeight) / (this._terminalInstances.length + 1)); if (newTerminalSize < TERMINAL_MIN_USEFUL_SIZE) { return undefined; } - const instance = this._terminalService.createInstance( - terminalFocusContextKey, - configHelper, - undefined, - shellLaunchConfig); + const instance = this._terminalService.createInstance(undefined, shellLaunchConfig); this._terminalInstances.splice(this._activeInstanceIndex + 1, 0, instance); this._initInstanceListeners(instance); this._setActiveInstance(instance); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalWidgetManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalWidgetManager.ts index 8660347598..87a7162ed6 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalWidgetManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalWidgetManager.ts @@ -8,10 +8,10 @@ import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle' const WIDGET_HEIGHT = 29; export class TerminalWidgetManager implements IDisposable { - private _container: HTMLElement | null; - private _xtermViewport: HTMLElement | null; + private _container: HTMLElement | undefined; + private _xtermViewport: HTMLElement | undefined; - private _messageWidget: MessageWidget; + private _messageWidget: MessageWidget | undefined; private readonly _messageListeners = new DisposableStore(); constructor( @@ -27,9 +27,9 @@ export class TerminalWidgetManager implements IDisposable { public dispose(): void { if (this._container && this._container.parentElement) { this._container.parentElement.removeChild(this._container); - this._container = null; + this._container = undefined; } - this._xtermViewport = null; + this._xtermViewport = undefined; this._messageListeners.dispose(); } @@ -108,4 +108,4 @@ class MessageWidget { this._container.removeChild(this.domNode); } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index db75dd8ddc..c1518c06e2 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -76,6 +76,11 @@ export interface ITerminalConfiguration { osx: string | null; windows: string | null; }; + automationShell: { + linux: string | null; + osx: string | null; + windows: string | null; + }; shellArgs: { linux: string[]; osx: string[]; @@ -187,11 +192,6 @@ export interface IShellLaunchConfig { */ initialText?: string; - /** - * @deprecated use `isExtensionTerminal` - */ - isRendererOnly?: boolean; - /** * Whether an extension is controlling the terminal via a `vscode.Pseudoterminal`. */ @@ -244,16 +244,10 @@ export interface ITerminalService { */ createTerminal(shell?: IShellLaunchConfig): ITerminalInstance; - /** - * Creates a terminal renderer. - * @param name The name of the terminal. - */ - createTerminalRenderer(name: string): ITerminalInstance; - /** * Creates a raw terminal instance, this should not be used outside of the terminal part. */ - createInstance(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance; + createInstance(container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance; getInstanceFromId(terminalId: number): ITerminalInstance | undefined; getInstanceFromIndex(terminalIndex: number): ITerminalInstance; getTabLabels(): string[]; @@ -422,13 +416,6 @@ export interface ITerminalInstance { */ onData: Event; - /** - * Attach a listener to the "renderer" input event, this event fires for terminal renderers on - * keystrokes and when the Terminal.sendText extension API is used. - * @param listener The listener function. - */ - onRendererInput: Event; - /** * Attach a listener to listen for new lines added to this terminal instance. * @@ -483,7 +470,7 @@ export interface ITerminalInstance { * An object that tracks when commands are run and enables navigating and selecting between * them. */ - readonly commandTracker: ITerminalCommandTracker; + readonly commandTracker: ICommandTracker | undefined; readonly navigationMode: INavigationMode | undefined; @@ -497,14 +484,6 @@ export interface ITerminalInstance { */ dispose(immediate?: boolean): void; - /** - * Indicates that a consumer of a renderer only terminal is finished with it. - * - * @param exitCode The exit code of the terminal. Zero indicates success, non-zero indicates - * failure. - */ - rendererExit(exitCode: number): void; - /** * Forces the terminal to redraw its viewport. */ @@ -671,7 +650,7 @@ export interface ITerminalInstance { getCwd(): Promise; } -export interface ITerminalCommandTracker { +export interface ICommandTracker { scrollToPreviousCommand(): void; scrollToNextCommand(): void; selectToPreviousCommand(): void; @@ -697,7 +676,7 @@ export interface IBeforeProcessDataEvent { export interface ITerminalProcessManager extends IDisposable { readonly processState: ProcessState; readonly ptyProcessReady: Promise; - readonly shellProcessId: number; + readonly shellProcessId: number | undefined; readonly remoteAuthority: string | undefined; readonly os: OperatingSystem | undefined; readonly userHome: string | undefined; @@ -780,7 +759,8 @@ export interface IAvailableShellsRequest { } export interface IDefaultShellAndArgsRequest { - (shell: string, args: string[] | string | undefined): void; + useAutomationShell: boolean; + callback: (shell: string, args: string[] | string | undefined) => void; } export enum LinuxDistro { diff --git a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts index 3045d9aeba..d95984ab93 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts @@ -197,47 +197,61 @@ export function getDefaultShell( lastActiveWorkspace: IWorkspaceFolder | undefined, configurationResolverService: IConfigurationResolverService | undefined, logService: ILogService, + useAutomationShell: boolean, platformOverride: platform.Platform = platform.platform ): string { - const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux'; - const shellConfigValue = fetchSetting(`terminal.integrated.shell.${platformKey}`); - let executable = (isWorkspaceShellAllowed ? shellConfigValue.value : shellConfigValue.user) || (shellConfigValue.default || defaultShell); + let maybeExecutable: string | null = null; + if (useAutomationShell) { + // If automationShell is specified, this should override the normal setting + maybeExecutable = getShellSetting(fetchSetting, isWorkspaceShellAllowed, 'automationShell', platformOverride); + } + if (!maybeExecutable) { + maybeExecutable = getShellSetting(fetchSetting, isWorkspaceShellAllowed, 'shell', platformOverride); + } + maybeExecutable = maybeExecutable || defaultShell; // Change Sysnative to System32 if the OS is Windows but NOT WoW64. It's // safe to assume that this was used by accident as Sysnative does not // exist and will break the terminal in non-WoW64 environments. if ((platformOverride === platform.Platform.Windows) && !isWoW64 && windir) { - const sysnativePath = path.join(windir, 'Sysnative').toLowerCase(); - if (executable && executable.toLowerCase().indexOf(sysnativePath) === 0) { - executable = path.join(windir, 'System32', executable.substr(sysnativePath.length)); + const sysnativePath = path.join(windir, 'Sysnative').replace(/\//g, '\\').toLowerCase(); + if (maybeExecutable && maybeExecutable.toLowerCase().indexOf(sysnativePath) === 0) { + maybeExecutable = path.join(windir, 'System32', maybeExecutable.substr(sysnativePath.length + 1)); } } // Convert / to \ on Windows for convenience - if (executable && platformOverride === platform.Platform.Windows) { - executable = executable.replace(/\//g, '\\'); + if (maybeExecutable && platformOverride === platform.Platform.Windows) { + maybeExecutable = maybeExecutable.replace(/\//g, '\\'); } if (configurationResolverService) { try { - executable = configurationResolverService.resolve(lastActiveWorkspace, executable); + maybeExecutable = configurationResolverService.resolve(lastActiveWorkspace, maybeExecutable); } catch (e) { - logService.error(`Could not resolve terminal.integrated.shell.${platformKey}`, e); - executable = executable; + logService.error(`Could not resolve shell`, e); + maybeExecutable = maybeExecutable; } } - return executable; + return maybeExecutable; } export function getDefaultShellArgs( fetchSetting: (key: string) => { user: string | string[] | undefined, value: string | string[] | undefined, default: string | string[] | undefined }, isWorkspaceShellAllowed: boolean, + useAutomationShell: boolean, lastActiveWorkspace: IWorkspaceFolder | undefined, configurationResolverService: IConfigurationResolverService | undefined, logService: ILogService, platformOverride: platform.Platform = platform.platform, ): string | string[] { + if (useAutomationShell) { + if (!!getShellSetting(fetchSetting, isWorkspaceShellAllowed, 'automationShell', platformOverride)) { + return []; + } + } + const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux'; const shellArgsConfigValue = fetchSetting(`terminal.integrated.shellArgs.${platformKey}`); let args = ((isWorkspaceShellAllowed ? shellArgsConfigValue.value : shellArgsConfigValue.user) || shellArgsConfigValue.default); @@ -259,6 +273,18 @@ export function getDefaultShellArgs( return args; } +function getShellSetting( + fetchSetting: (key: string) => { user: string | string[] | undefined, value: string | string[] | undefined, default: string | string[] | undefined }, + isWorkspaceShellAllowed: boolean, + type: 'automationShell' | 'shell', + platformOverride: platform.Platform = platform.platform, +): string | null { + const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux'; + const shellConfigValue = fetchSetting(`terminal.integrated.${type}.${platformKey}`); + const executable = (isWorkspaceShellAllowed ? shellConfigValue.value : shellConfigValue.user) || (shellConfigValue.default); + return executable; +} + export function createTerminalEnvironment( shellLaunchConfig: IShellLaunchConfig, lastActiveWorkspace: IWorkspaceFolder | null, diff --git a/src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts b/src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts index 10da7571df..5661101a87 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts @@ -18,13 +18,13 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal public readonly onProcessData: Event = this._onProcessData.event; private readonly _onProcessExit = this._register(new Emitter()); public readonly onProcessExit: Event = this._onProcessExit.event; - private readonly _onProcessReady = new Emitter<{ pid: number, cwd: string }>(); + private readonly _onProcessReady = this._register(new Emitter<{ pid: number, cwd: string }>()); public get onProcessReady(): Event<{ pid: number, cwd: string }> { return this._onProcessReady.event; } private readonly _onProcessTitleChanged = this._register(new Emitter()); public readonly onProcessTitleChanged: Event = this._onProcessTitleChanged.event; - private readonly _onProcessOverrideDimensions = new Emitter(); + private readonly _onProcessOverrideDimensions = this._register(new Emitter()); public get onProcessOverrideDimensions(): Event { return this._onProcessOverrideDimensions.event; } - private readonly _onProcessResolvedShellLaunchConfig = new Emitter(); + private readonly _onProcessResolvedShellLaunchConfig = this._register(new Emitter()); public get onProcessResolvedShellLaunchConfig(): Event { return this._onProcessResolvedShellLaunchConfig.event; } private readonly _onInput = this._register(new Emitter()); diff --git a/src/vs/workbench/contrib/terminal/common/terminalService.ts b/src/vs/workbench/contrib/terminal/common/terminalService.ts index d66b9c5d40..e343191a67 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalService.ts @@ -35,7 +35,7 @@ export abstract class TerminalService implements ITerminalService { protected _isShuttingDown: boolean; protected _terminalFocusContextKey: IContextKey; protected _findWidgetVisible: IContextKey; - protected _terminalContainer: HTMLElement; + protected _terminalContainer: HTMLElement | undefined; protected _terminalTabs: ITerminalTab[] = []; protected _backgroundedTerminalInstances: ITerminalInstance[] = []; protected get _terminalInstances(): ITerminalInstance[] { @@ -123,13 +123,9 @@ export abstract class TerminalService implements ITerminalService { protected abstract _showBackgroundTerminal(instance: ITerminalInstance): void; public abstract createTerminal(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance; - public abstract createInstance(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, container: HTMLElement, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance; + public abstract createInstance(container: HTMLElement, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance; public abstract setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void; - public createTerminalRenderer(name: string): ITerminalInstance { - return this.createTerminal({ name, isRendererOnly: true }); - } - public getActiveOrCreateInstance(wasNewTerminalAction?: boolean): ITerminalInstance { const activeInstance = this.getActiveInstance(); return activeInstance ? activeInstance : this.createTerminal(undefined, wasNewTerminalAction); diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts index e380d337f7..377f3fbbd1 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts @@ -73,7 +73,7 @@ export class TerminalInstanceService implements ITerminalInstanceService { return this._storageService.getBoolean(IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, StorageScope.WORKSPACE, false); } - public getDefaultShellAndArgs(platformOverride: Platform = platform): Promise<{ shell: string, args: string | string[] }> { + public getDefaultShellAndArgs(useAutomationShell: boolean, platformOverride: Platform = platform): Promise<{ shell: string, args: string | string[] }> { const isWorkspaceShellAllowed = this._isWorkspaceShellAllowed(); const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(); let lastActiveWorkspace = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) : undefined; @@ -87,11 +87,13 @@ export class TerminalInstanceService implements ITerminalInstanceService { lastActiveWorkspace, this._configurationResolverService, this._logService, + useAutomationShell, platformOverride ); const args = getDefaultShellArgs( (key) => this._configurationService.inspect(key), isWorkspaceShellAllowed, + useAutomationShell, lastActiveWorkspace, this._configurationResolverService, this._logService, diff --git a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts index 98046de6b6..3946d19993 100644 --- a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts +++ b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts @@ -10,7 +10,7 @@ import * as pty from 'node-pty'; import * as fs from 'fs'; import { Event, Emitter } from 'vs/base/common/event'; import { getWindowsBuildNumber } from 'vs/workbench/contrib/terminal/node/terminal'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { IShellLaunchConfig, ITerminalChildProcess, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE, SHELL_CWD_INVALID_EXIT_CODE } from 'vs/workbench/contrib/terminal/common/terminal'; import { exec } from 'child_process'; import { ILogService } from 'vs/platform/log/common/log'; @@ -18,23 +18,23 @@ import { stat } from 'vs/base/node/pfs'; import { findExecutable } from 'vs/workbench/contrib/terminal/node/terminalEnvironment'; import { URI } from 'vs/base/common/uri'; -export class TerminalProcess implements ITerminalChildProcess, IDisposable { - private _exitCode: number; +export class TerminalProcess extends Disposable implements ITerminalChildProcess { + private _exitCode: number | undefined; private _closeTimeout: any; private _ptyProcess: pty.IPty | undefined; private _currentTitle: string = ''; - private _processStartupComplete: Promise; + private _processStartupComplete: Promise | undefined; private _isDisposed: boolean = false; private _titleInterval: NodeJS.Timer | null = null; private _initialCwd: string; - private readonly _onProcessData = new Emitter(); + private readonly _onProcessData = this._register(new Emitter()); public get onProcessData(): Event { return this._onProcessData.event; } - private readonly _onProcessExit = new Emitter(); + private readonly _onProcessExit = this._register(new Emitter()); public get onProcessExit(): Event { return this._onProcessExit.event; } - private readonly _onProcessReady = new Emitter<{ pid: number, cwd: string }>(); + private readonly _onProcessReady = this._register(new Emitter<{ pid: number, cwd: string }>()); public get onProcessReady(): Event<{ pid: number, cwd: string }> { return this._onProcessReady.event; } - private readonly _onProcessTitleChanged = new Emitter(); + private readonly _onProcessTitleChanged = this._register(new Emitter()); public get onProcessTitleChanged(): Event { return this._onProcessTitleChanged.event; } constructor( @@ -46,6 +46,7 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { windowsEnableConpty: boolean, @ILogService private readonly _logService: ILogService ) { + super(); let shellName: string; if (os.platform() === 'win32') { shellName = path.basename(shellLaunchConfig.executable || ''); @@ -65,7 +66,8 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { cols, rows, experimentalUseConpty: useConpty, - conptyInheritCursor: true + // This option will force conpty to not redraw the whole viewport on launch + conptyInheritCursor: useConpty && !!shellLaunchConfig.initialText }; const cwdVerification = stat(cwd).then(async stat => { @@ -87,7 +89,9 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { }, async (err) => { if (err && err.code === 'ENOENT') { let cwd = shellLaunchConfig.cwd instanceof URI ? shellLaunchConfig.cwd.path : shellLaunchConfig.cwd!; - const executable = await findExecutable(shellLaunchConfig.executable!, cwd); + // Try to get path + const envPaths: string[] | undefined = (shellLaunchConfig.env && shellLaunchConfig.env.PATH) ? shellLaunchConfig.env.PATH.split(path.delimiter) : undefined; + const executable = await findExecutable(shellLaunchConfig.executable!, cwd, envPaths); if (!executable) { return Promise.reject(SHELL_PATH_INVALID_EXIT_CODE); } @@ -143,6 +147,7 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { this._onProcessExit.dispose(); this._onProcessReady.dispose(); this._onProcessTitleChanged.dispose(); + super.dispose(); } private _setupTitlePolling(ptyProcess: pty.IPty) { @@ -172,7 +177,7 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { private _kill(): void { // Wait to kill to process until the start up code has run. This prevents us from firing a process exit before a // process start. - this._processStartupComplete.then(() => { + this._processStartupComplete!.then(() => { if (this._isDisposed) { return; } @@ -186,7 +191,7 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { } catch (ex) { // Swallow, the pty has already been killed } - this._onProcessExit.fire(this._exitCode); + this._onProcessExit.fire(this._exitCode || 0); this.dispose(); }); } diff --git a/src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts b/src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts index dea63ca619..badb8a1756 100644 --- a/src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts +++ b/src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts @@ -8,6 +8,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { ITerminalInstance, IWindowsShellHelper } from 'vs/workbench/contrib/terminal/common/terminal'; import { Terminal as XTermTerminal } from 'xterm'; import WindowsProcessTreeType = require('windows-process-tree'); +import { Disposable } from 'vs/base/common/lifecycle'; const SHELL_EXECUTABLES = [ 'cmd.exe', @@ -24,17 +25,19 @@ const SHELL_EXECUTABLES = [ let windowsProcessTree: typeof WindowsProcessTreeType; -export class WindowsShellHelper implements IWindowsShellHelper { - private _onCheckShell: Emitter | undefined>; +export class WindowsShellHelper extends Disposable implements IWindowsShellHelper { + private _onCheckShell: Emitter | undefined> = this._register(new Emitter | undefined>()); private _isDisposed: boolean; - private _currentRequest: Promise | null; - private _newLineFeed: boolean; + private _currentRequest: Promise | undefined; + private _newLineFeed: boolean = false; public constructor( private _rootProcessId: number, private _terminalInstance: ITerminalInstance, private _xterm: XTermTerminal ) { + super(); + if (!platform.isWindows) { throw new Error(`WindowsShellHelper cannot be instantiated on ${platform.platform}`); } @@ -47,7 +50,6 @@ export class WindowsShellHelper implements IWindowsShellHelper { } windowsProcessTree = mod; - this._onCheckShell = new Emitter>(); // The debounce is necessary to prevent multiple processes from spawning when // the enter key or output is spammed Event.debounce(this._onCheckShell.event, (l, e) => e, 150, true)(() => { @@ -65,6 +67,7 @@ export class WindowsShellHelper implements IWindowsShellHelper { this._xterm.onCursorMove(() => { if (this._newLineFeed) { this._onCheckShell.fire(undefined); + this._newLineFeed = false; } }); @@ -111,6 +114,7 @@ export class WindowsShellHelper implements IWindowsShellHelper { public dispose(): void { this._isDisposed = true; + super.dispose(); } /** @@ -127,7 +131,7 @@ export class WindowsShellHelper implements IWindowsShellHelper { this._currentRequest = new Promise(resolve => { windowsProcessTree.getProcessTree(this._rootProcessId, (tree) => { const name = this.traverseTree(tree); - this._currentRequest = null; + this._currentRequest = undefined; resolve(name); }); }); diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts index fd7c7226ce..6f3f9bae94 100644 --- a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { Terminal, TerminalCore } from 'xterm'; -import { TerminalCommandTracker } from 'vs/workbench/contrib/terminal/browser/terminalCommandTracker'; +import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon'; import { isWindows } from 'vs/base/common/platform'; interface TestTerminalCore extends TerminalCore { diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts index 23dd24db04..04b2492e4c 100644 --- a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts @@ -65,7 +65,7 @@ interface LinkFormatInfo { suite('Workbench - TerminalLinkHandler', () => { suite('localLinkRegex', () => { test('Windows', () => { - const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm(), { + const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Windows, userHome: '' } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); @@ -141,7 +141,7 @@ suite('Workbench - TerminalLinkHandler', () => { }); test('Linux', () => { - const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm(), { + const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '' } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); @@ -209,7 +209,7 @@ suite('Workbench - TerminalLinkHandler', () => { suite('preprocessPath', () => { test('Windows', () => { - const linkHandler = new TestTerminalLinkHandler(new TestXterm(), { + const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Windows, userHome: 'C:\\Users\\Me' } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); @@ -222,7 +222,7 @@ suite('Workbench - TerminalLinkHandler', () => { assert.equal(linkHandler.preprocessPath('C:\\absolute\\path\\file5'), 'C:\\absolute\\path\\file5'); }); test('Windows - spaces', () => { - const linkHandler = new TestTerminalLinkHandler(new TestXterm(), { + const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Windows, userHome: 'C:\\Users\\M e' } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); @@ -236,7 +236,7 @@ suite('Workbench - TerminalLinkHandler', () => { }); test('Linux', () => { - const linkHandler = new TestTerminalLinkHandler(new TestXterm(), { + const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '/home/me' } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); @@ -249,7 +249,7 @@ suite('Workbench - TerminalLinkHandler', () => { }); test('No Workspace', () => { - const linkHandler = new TestTerminalLinkHandler(new TestXterm(), { + const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '/home/me' } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); @@ -263,7 +263,7 @@ suite('Workbench - TerminalLinkHandler', () => { test('gitDiffLinkRegex', () => { // The platform is irrelevant because the links generated by Git are the same format regardless of platform - const linkHandler = new TestTerminalLinkHandler(new TestXterm(), { + const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '' } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); diff --git a/src/vs/workbench/contrib/terminal/test/node/terminalEnvironment.test.ts b/src/vs/workbench/contrib/terminal/test/node/terminalEnvironment.test.ts index 1ea4b43674..e1f9894ffe 100644 --- a/src/vs/workbench/contrib/terminal/test/node/terminalEnvironment.test.ts +++ b/src/vs/workbench/contrib/terminal/test/node/terminalEnvironment.test.ts @@ -128,4 +128,48 @@ suite('Workbench - TerminalEnvironment', () => { assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [], ignoreConfigurationCwd: true }, '/userHome/', undefined, undefined, Uri.file('/bar'), '/foo'), '/bar'); }); }); + + suite('getDefaultShell', () => { + test('should change Sysnative to System32 in non-WoW64 systems', () => { + const shell = terminalEnvironment.getDefaultShell(key => { + return ({ + 'terminal.integrated.shell.windows': { user: 'C:\\Windows\\Sysnative\\cmd.exe', value: undefined, default: undefined } + } as any)[key]; + }, false, 'DEFAULT', false, 'C:\\Windows', undefined, undefined, {} as any, false, platform.Platform.Windows); + assert.equal(shell, 'C:\\Windows\\System32\\cmd.exe'); + }); + + test('should not change Sysnative to System32 in WoW64 systems', () => { + const shell = terminalEnvironment.getDefaultShell(key => { + return ({ + 'terminal.integrated.shell.windows': { user: 'C:\\Windows\\Sysnative\\cmd.exe', value: undefined, default: undefined } + } as any)[key]; + }, false, 'DEFAULT', true, 'C:\\Windows', undefined, undefined, {} as any, false, platform.Platform.Windows); + assert.equal(shell, 'C:\\Windows\\Sysnative\\cmd.exe'); + }); + + test('should use automationShell when specified', () => { + const shell1 = terminalEnvironment.getDefaultShell(key => { + return ({ + 'terminal.integrated.shell.windows': { user: 'shell', value: undefined, default: undefined }, + 'terminal.integrated.automationShell.windows': { user: undefined, value: undefined, default: undefined } + } as any)[key]; + }, false, 'DEFAULT', false, 'C:\\Windows', undefined, undefined, {} as any, false, platform.Platform.Windows); + assert.equal(shell1, 'shell', 'automationShell was false'); + const shell2 = terminalEnvironment.getDefaultShell(key => { + return ({ + 'terminal.integrated.shell.windows': { user: 'shell', value: undefined, default: undefined }, + 'terminal.integrated.automationShell.windows': { user: undefined, value: undefined, default: undefined } + } as any)[key]; + }, false, 'DEFAULT', false, 'C:\\Windows', undefined, undefined, {} as any, true, platform.Platform.Windows); + assert.equal(shell2, 'shell', 'automationShell was true'); + const shell3 = terminalEnvironment.getDefaultShell(key => { + return ({ + 'terminal.integrated.shell.windows': { user: 'shell', value: undefined, default: undefined }, + 'terminal.integrated.automationShell.windows': { user: 'automationShell', value: undefined, default: undefined } + } as any)[key]; + }, false, 'DEFAULT', false, 'C:\\Windows', undefined, undefined, {} as any, true, platform.Platform.Windows); + assert.equal(shell3, 'automationShell', 'automationShell was true and specified in settings'); + }); + }); }); diff --git a/src/vs/workbench/contrib/update/electron-browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/electron-browser/releaseNotesEditor.ts index 714239b4de..32dbf2bca8 100644 --- a/src/vs/workbench/contrib/update/electron-browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/electron-browser/releaseNotesEditor.ts @@ -28,24 +28,6 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { generateUuid } from 'vs/base/common/uuid'; -function renderBody( - body: string, - css: string -): string { - const styleSheetPath = require.toUrl('./media/markdown.css').replace('file://', 'vscode-resource://'); - return ` - - - - - - - - - ${body} - `; -} - export class ReleaseNotesManager { private readonly _releaseNotesCache = new Map>(); @@ -188,8 +170,18 @@ export class ReleaseNotesManager { const content = await this.renderContent(text); const colorMap = TokenizationRegistry.getColorMap(); const css = colorMap ? generateTokensCSSForColorMap(colorMap) : ''; - const body = renderBody(content, css); - return body; + const styleSheetPath = require.toUrl('./media/markdown.css').replace('file://', 'vscode-resource://'); + return ` + + + + + + + + + ${content} + `; } private async renderContent(text: string): Promise { diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html index ac53ce590e..e301f5ea90 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index.html @@ -3,9 +3,6 @@ - - Virtual Document @@ -16,4 +13,4 @@ - \ No newline at end of file + diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts index 34ddf4fa33..995a9385b3 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts @@ -45,7 +45,7 @@ export class WebviewEditorInputFactory implements IEditorInputFactory { const data: SerializedWebview = { viewType: input.viewType, title: input.getName(), - options: input.webview.options, + options: { ...input.webview.options, ...input.webview.contentOptions }, extensionLocation: input.extension ? input.extension.location : undefined, extensionId: input.extension && input.extension.id ? input.extension.id.value : undefined, state: input.webview.state, @@ -118,4 +118,4 @@ function reviveState(state: unknown | undefined): undefined | string { return (state as any).state; } return undefined; -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts index 30f60bec8a..7194e52c22 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts @@ -15,6 +15,7 @@ import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor import { ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; import { RevivedWebviewEditorInput, WebviewEditorInput } from './webviewEditorInput'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export const IWebviewEditorService = createDecorator('webviewEditorService'); @@ -264,3 +265,5 @@ export class WebviewEditorService implements IWebviewEditorService { return rootPaths; } } + +registerSingleton(IWebviewEditorService, WebviewEditorService, true); diff --git a/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts b/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts index 6778d00b96..a05e21a970 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SimpleFindWidget } from 'vs/editor/contrib/find/simpleFindWidget'; +import { SimpleFindWidget } from 'vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; @@ -54,4 +54,6 @@ export class WebviewFindWidget extends SimpleFindWidget { protected onFindInputFocusTrackerFocus() { } protected onFindInputFocusTrackerBlur() { } -} \ No newline at end of file + + protected findFirst() { } +} diff --git a/src/vs/workbench/contrib/webview/browser/webviewService.ts b/src/vs/workbench/contrib/webview/browser/webviewService.ts index 4aa51c0116..eec65329d0 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewService.ts @@ -7,6 +7,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement'; import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview'; import { DynamicWebviewEditorOverlay } from './dynamicWebviewEditorOverlay'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class WebviewService implements IWebviewService { _serviceBrand: any; @@ -31,3 +32,5 @@ export class WebviewService implements IWebviewService { return this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions); } } + +registerSingleton(IWebviewService, WebviewService, true); diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index 055bf76361..512901e5f0 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -566,7 +566,7 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview { } public layout(): void { - if (!this._webview) { + if (!this._webview || this._webview.style.width === '0px') { return; } const contents = this._webview.getWebContents(); diff --git a/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts index a48253928e..6320295b3b 100644 --- a/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts +++ b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts @@ -9,7 +9,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ShowAllCommandsAction } from 'vs/workbench/contrib/quickopen/browser/commandsHandler'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { localize } from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; @@ -168,10 +168,8 @@ class WelcomeOverlay extends Disposable { } private create(): void { - const container = this.layoutService.getContainer(Parts.EDITOR_PART)!; - const offset = this.layoutService.getTitleBarOffset(); - this._overlay = dom.append(container.parentElement!, $('.welcomeOverlay')); + this._overlay = dom.append(this.layoutService.getWorkbenchElement(), $('.welcomeOverlay')); this._overlay.style.top = `${offset}px`; this._overlay.style.height = `calc(100% - ${offset}px)`; this._overlay.style.display = 'none'; diff --git a/src/vs/workbench/electron-browser/actions/windowActions.ts b/src/vs/workbench/electron-browser/actions/windowActions.ts index 7361023329..48afbd50ad 100644 --- a/src/vs/workbench/electron-browser/actions/windowActions.ts +++ b/src/vs/workbench/electron-browser/actions/windowActions.ts @@ -15,7 +15,6 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IQuickInputService, IQuickInputButton } from 'vs/platform/quickinput/common/quickInput'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; -import product from 'vs/platform/product/node/product'; import { ICommandHandler } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -55,8 +54,12 @@ export class NewWindowAction extends Action { } export abstract class BaseZoomAction extends Action { + private static readonly SETTING_KEY = 'window.zoomLevel'; + private static readonly MAX_ZOOM_LEVEL = 9; + private static readonly MIN_ZOOM_LEVEL = -8; + constructor( id: string, label: string, @@ -68,6 +71,10 @@ export abstract class BaseZoomAction extends Action { protected async setConfiguredZoomLevel(level: number): Promise { level = Math.round(level); // when reaching smallest zoom, prevent fractional zoom levels + if (level > BaseZoomAction.MAX_ZOOM_LEVEL || level < BaseZoomAction.MIN_ZOOM_LEVEL) { + return; // https://github.com/microsoft/vscode/issues/48357 + } + const applyZoom = () => { webFrame.setZoomLevel(level); browser.setZoomFactor(webFrame.getZoomFactor()); @@ -267,25 +274,6 @@ export class QuickSwitchWindow extends BaseSwitchWindow { } } - -export class ShowAboutDialogAction extends Action { - - static readonly ID = 'workbench.action.showAboutDialog'; - static LABEL = nls.localize('about', "About {0}", product.applicationName); - - constructor( - id: string, - label: string, - @IWindowsService private readonly windowsService: IWindowsService - ) { - super(id, label); - } - - run(): Promise { - return this.windowsService.openAboutDialog(); - } -} - export const NewWindowTabHandler: ICommandHandler = function (accessor: ServicesAccessor) { return accessor.get(IWindowsService).newWindowTab(); }; diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index 94ed2fc297..5fe3e06edb 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -13,17 +13,16 @@ import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; import { KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, OpenTwitterUrlAction, OpenRequestFeatureUrlAction, OpenPrivacyStatementUrlAction, OpenLicenseUrlAction, OpenNewsletterSignupUrlAction } from 'vs/workbench/electron-browser/actions/helpActions'; import { ToggleSharedProcessAction, ToggleDevToolsAction } from 'vs/workbench/electron-browser/actions/developerActions'; -import { ShowAboutDialogAction, ZoomResetAction, ZoomOutAction, ZoomInAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, QuickSwitchWindow, ReloadWindowWithExtensionsDisabledAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler } from 'vs/workbench/electron-browser/actions/windowActions'; -import { AddRootFolderAction, GlobalRemoveRootFolderAction, SaveWorkspaceAsAction, OpenWorkspaceConfigFileAction, DuplicateWorkspaceInNewWindowAction, CloseWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions'; +import { ZoomResetAction, ZoomOutAction, ZoomInAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, QuickSwitchWindow, ReloadWindowWithExtensionsDisabledAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler } from 'vs/workbench/electron-browser/actions/windowActions'; +import { AddRootFolderAction, GlobalRemoveRootFolderAction, SaveWorkspaceAsAction, DuplicateWorkspaceInNewWindowAction, CloseWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ADD_ROOT_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; import { SupportsWorkspacesContext, IsMacContext, HasMacNativeTabsContext, IsDevelopmentContext, WorkbenchStateContext, WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys'; import { NoEditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/common/editor'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; -import product from 'vs/platform/product/node/product'; import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; // {{SQL CARBON EDIT}} add import @@ -55,7 +54,7 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten registry.registerWorkbenchAction(new SyncActionDescriptor(QuickSwitchWindow, QuickSwitchWindow.ID, QuickSwitchWindow.LABEL), 'Quick Switch Window...'); KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'workbench.action.closeWindow', // close the window when the last editor is closed by reusing the same keybinding + id: CloseCurrentWindowAction.ID, // close the window when the last editor is closed by reusing the same keybinding weight: KeybindingWeight.WorkbenchContrib, when: ContextKeyExpr.and(NoEditorsVisibleContext, SingleEditorGroupsContext), primary: KeyMod.CtrlCmd | KeyCode.KEY_W, @@ -86,18 +85,6 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalRemoveRootFolderAction, GlobalRemoveRootFolderAction.ID, GlobalRemoveRootFolderAction.LABEL), 'Workspaces: Remove Folder from Workspace...', workspacesCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(SaveWorkspaceAsAction, SaveWorkspaceAsAction.ID, SaveWorkspaceAsAction.LABEL), 'Workspaces: Save Workspace As...', workspacesCategory, SupportsWorkspacesContext); registry.registerWorkbenchAction(new SyncActionDescriptor(DuplicateWorkspaceInNewWindowAction, DuplicateWorkspaceInNewWindowAction.ID, DuplicateWorkspaceInNewWindowAction.LABEL), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory); - - CommandsRegistry.registerCommand(OpenWorkspaceConfigFileAction.ID, serviceAccessor => { - serviceAccessor.get(IInstantiationService).createInstance(OpenWorkspaceConfigFileAction, OpenWorkspaceConfigFileAction.ID, OpenWorkspaceConfigFileAction.LABEL).run(); - }); - - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: OpenWorkspaceConfigFileAction.ID, - title: { value: `${workspacesCategory}: ${OpenWorkspaceConfigFileAction.LABEL}`, original: 'Workspaces: Open Workspace Configuration File' }, - }, - when: WorkbenchStateContext.isEqualTo('workspace') - }); })(); // Actions: macOS Native Tabs @@ -165,7 +152,6 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten registry.registerWorkbenchAction(new SyncActionDescriptor(OpenRequestFeatureUrlAction, OpenRequestFeatureUrlAction.ID, OpenRequestFeatureUrlAction.LABEL), 'Help: Search Feature Requests', helpCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLicenseUrlAction, OpenLicenseUrlAction.ID, OpenLicenseUrlAction.LABEL), 'Help: View License', helpCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPrivacyStatementUrlAction, OpenPrivacyStatementUrlAction.ID, OpenPrivacyStatementUrlAction.LABEL), 'Help: Privacy Statement', helpCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAboutDialogAction, ShowAboutDialogAction.ID, ShowAboutDialogAction.LABEL), `Help: About ${product.applicationName}`, helpCategory); })(); })(); @@ -283,7 +269,7 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '1_welcome', command: { - id: 'workbench.action.openDocumentationUrl', + id: OpenDocumentationUrlAction.ID, title: nls.localize({ key: 'miDocumentation', comment: ['&& denotes a mnemonic'] }, "&&Documentation") }, order: 3 @@ -303,7 +289,7 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '2_reference', command: { - id: 'workbench.action.keybindingsReference', + id: KeybindingsReferenceAction.ID, title: nls.localize({ key: 'miKeyboardShortcuts', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts Reference") }, order: 1 @@ -312,7 +298,7 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '2_reference', command: { - id: 'workbench.action.openIntroductoryVideosUrl', + id: OpenIntroductoryVideosUrlAction.ID, title: nls.localize({ key: 'miIntroductoryVideos', comment: ['&& denotes a mnemonic'] }, "Introductory &&Videos") }, order: 2 @@ -321,7 +307,7 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '2_reference', command: { - id: 'workbench.action.openTipsAndTricksUrl', + id: OpenTipsAndTricksUrlAction.ID, title: nls.localize({ key: 'miTipsAndTricks', comment: ['&& denotes a mnemonic'] }, "Tips and Tri&&cks") }, order: 3 @@ -331,7 +317,7 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '3_feedback', command: { - id: 'workbench.action.openTwitterUrl', + id: OpenTwitterUrlAction.ID, title: nls.localize({ key: 'miTwitter', comment: ['&& denotes a mnemonic'] }, "&&Join Us on Twitter") }, order: 1 @@ -340,7 +326,7 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '3_feedback', command: { - id: 'workbench.action.openRequestFeatureUrl', + id: OpenRequestFeatureUrlAction.ID, title: nls.localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Search Feature Requests") }, order: 2 @@ -360,7 +346,7 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '4_legal', command: { - id: 'workbench.action.openLicenseUrl', + id: OpenLicenseUrlAction.ID, title: nls.localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "View &&License") }, order: 1 @@ -369,7 +355,7 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '4_legal', command: { - id: 'workbench.action.openPrivacyStatementUrl', + id: OpenPrivacyStatementUrlAction.ID, title: nls.localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "Privac&&y Statement") }, order: 2 @@ -379,7 +365,7 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '5_tools', command: { - id: 'workbench.action.toggleDevTools', + id: ToggleDevToolsAction.ID, title: nls.localize({ key: 'miToggleDevTools', comment: ['&& denotes a mnemonic'] }, "&&Toggle Developer Tools") }, order: 1 @@ -393,17 +379,6 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten }, order: 2 }); - - // About - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: 'z_about', - command: { - id: 'workbench.action.showAboutDialog', - title: nls.localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About") - }, - order: 1, - when: IsMacContext.toNegated() - }); })(); // Configuration @@ -519,7 +494,7 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten 'default': true, 'description': nls.localize('window.nativeFullScreen', "Controls if native full-screen should be used on macOS. Disable this option to prevent macOS from creating a new space when going full-screen."), 'scope': ConfigurationScope.APPLICATION, - 'included': false /* isMacintosh */ + 'included': isMacintosh }, 'window.clickThroughInactive': { 'type': 'boolean', diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index c99e1e1515..7d070169c6 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -54,7 +54,6 @@ import { basename } from 'vs/base/common/resources'; class CodeRendererMain extends Disposable { - private workbench: Workbench; private readonly environmentService: WorkbenchEnvironmentService; constructor(configuration: IWindowConfiguration) { @@ -116,17 +115,17 @@ class CodeRendererMain extends Disposable { mark('willStartWorkbench'); // Create Workbench - this.workbench = new Workbench(document.body, services.serviceCollection, services.logService); + const workbench = new Workbench(document.body, services.serviceCollection, services.logService); // Layout - this._register(addDisposableListener(window, EventType.RESIZE, e => this.onWindowResize(e, true))); + this._register(addDisposableListener(window, EventType.RESIZE, e => this.onWindowResize(e, true, workbench))); // Workbench Lifecycle - this._register(this.workbench.onShutdown(() => this.dispose())); - this._register(this.workbench.onWillShutdown(event => event.join(services.storageService.close()))); + this._register(workbench.onShutdown(() => this.dispose())); + this._register(workbench.onWillShutdown(event => event.join(services.storageService.close()))); // Startup - const instantiationService = this.workbench.startup(); + const instantiationService = workbench.startup(); // Window this._register(instantiationService.createInstance(ElectronWindow)); @@ -145,7 +144,7 @@ class CodeRendererMain extends Disposable { services.logService.trace('workbench configuration', JSON.stringify(this.environmentService.configuration)); } - private onWindowResize(e: Event, retry: boolean): void { + private onWindowResize(e: Event, retry: boolean, workbench: Workbench): void { if (e.target === window) { if (window.document && window.document.body && window.document.body.clientWidth === 0) { // TODO@Ben this is an electron issue on macOS when simple fullscreen is enabled @@ -154,12 +153,12 @@ class CodeRendererMain extends Disposable { // call at the next animation frame once, in the hope that the dimensions are // proper then. if (retry) { - scheduleAtNextAnimationFrame(() => this.onWindowResize(e, false)); + scheduleAtNextAnimationFrame(() => this.onWindowResize(e, false, workbench)); } return; } - this.workbench.layout(); + workbench.layout(); } } @@ -168,7 +167,7 @@ class CodeRendererMain extends Disposable { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // NOTE: DO NOT ADD ANY OTHER SERVICE INTO THE COLLECTION HERE. - // CONTRIBUTE IT VIA WORKBENCH.MAIN.TS AND registerSingleton(). + // CONTRIBUTE IT VIA WORKBENCH.DESKTOP.MAIN.TS AND registerSingleton(). // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // Main Process diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 3c4fabcdf8..8cee13b607 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -213,6 +213,8 @@ export class ElectronWindow extends Disposable { this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('window.zoomLevel')) { this.updateWindowZoomLevel(); + } else if (e.affectsConfiguration('keyboard.touchbar.enabled') || e.affectsConfiguration('keyboard.touchbar.ignored')) { + this.updateTouchbarMenu(); } })); @@ -340,11 +342,8 @@ export class ElectronWindow extends Disposable { } private updateTouchbarMenu(): void { - if ( - !isMacintosh || // macOS only - !this.configurationService.getValue('keyboard.touchbar.enabled') // disabled via setting - ) { - return; + if (!isMacintosh) { + return; // macOS only } // Dispose old @@ -366,31 +365,40 @@ export class ElectronWindow extends Disposable { const actions: Array = []; + const disabled = this.configurationService.getValue('keyboard.touchbar.enabled') === false; + const ignoredItems = this.configurationService.getValue('keyboard.touchbar.ignored') || []; + // Fill actions into groups respecting order this.touchBarDisposables.add(createAndFillInActionBarActions(this.touchBarMenu, undefined, actions)); // Convert into command action multi array const items: ICommandAction[][] = []; let group: ICommandAction[] = []; - for (const action of actions) { + if (!disabled) { + for (const action of actions) { - // Command - if (action instanceof MenuItemAction) { - group.push(action.item); - } + // Command + if (action instanceof MenuItemAction) { + if (ignoredItems.indexOf(action.item.id) >= 0) { + continue; // ignored + } - // Separator - else if (action instanceof Separator) { - if (group.length) { - items.push(group); + group.push(action.item); } - group = []; - } - } + // Separator + else if (action instanceof Separator) { + if (group.length) { + items.push(group); + } - if (group.length) { - items.push(group); + group = []; + } + } + + if (group.length) { + items.push(group); + } } // Only update if the actions have changed diff --git a/src/vs/workbench/services/accessibility/node/accessibilityService.ts b/src/vs/workbench/services/accessibility/node/accessibilityService.ts index 91b0058663..1752979cd4 100644 --- a/src/vs/workbench/services/accessibility/node/accessibilityService.ts +++ b/src/vs/workbench/services/accessibility/node/accessibilityService.ts @@ -9,9 +9,12 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { AbstractAccessibilityService } from 'vs/platform/accessibility/common/abstractAccessibilityService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; export class AccessibilityService extends AbstractAccessibilityService implements IAccessibilityService { - _serviceBrand: any; + + _serviceBrand!: ServiceIdentifier; private _accessibilitySupport = AccessibilitySupport.Unknown; @@ -60,3 +63,5 @@ export class AccessibilityService extends AbstractAccessibilityService implement return this._accessibilitySupport; } } + +registerSingleton(IAccessibilityService, AccessibilityService, true); diff --git a/src/vs/workbench/services/backup/common/backupFileService.ts b/src/vs/workbench/services/backup/common/backupFileService.ts index b9a452d5df..b3f74b48cc 100644 --- a/src/vs/workbench/services/backup/common/backupFileService.ts +++ b/src/vs/workbench/services/backup/common/backupFileService.ts @@ -106,20 +106,15 @@ export class BackupFilesModel implements IBackupFilesModel { export class BackupFileService implements IBackupFileService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private impl: IBackupFileService; constructor( - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @IFileService fileService: IFileService + @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService, + @IFileService protected fileService: IFileService ) { - const backupWorkspaceResource = environmentService.configuration.backupWorkspaceResource; - if (backupWorkspaceResource) { - this.impl = new BackupFileServiceImpl(backupWorkspaceResource, this.hashPath, fileService); - } else { - this.impl = new InMemoryBackupFileService(this.hashPath); - } + this.initialize(); } protected hashPath(resource: URI): string { @@ -128,9 +123,25 @@ export class BackupFileService implements IBackupFileService { return hash(str).toString(16); } - initialize(backupWorkspaceResource: URI): void { + private initialize(): void { + const backupWorkspaceResource = this.environmentService.configuration.backupWorkspaceResource; + if (backupWorkspaceResource) { + this.impl = new BackupFileServiceImpl(backupWorkspaceResource, this.hashPath, this.fileService); + } else { + this.impl = new InMemoryBackupFileService(this.hashPath); + } + } + + reinitialize(): void { + + // Re-init implementation (unless we are running in-memory) if (this.impl instanceof BackupFileServiceImpl) { - this.impl.initialize(backupWorkspaceResource); + const backupWorkspaceResource = this.environmentService.configuration.backupWorkspaceResource; + if (backupWorkspaceResource) { + this.impl.initialize(backupWorkspaceResource); + } else { + this.impl = new InMemoryBackupFileService(this.hashPath); + } } } @@ -177,15 +188,15 @@ class BackupFileServiceImpl implements IBackupFileService { private static readonly PREAMBLE_META_SEPARATOR = ' '; // using a character that is know to be escaped in a URI as separator private static readonly PREAMBLE_MAX_LENGTH = 10000; - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; - private backupWorkspacePath: URI; + private backupWorkspacePath!: URI; private isShuttingDown: boolean; private ioOperationQueues: ResourceQueue; // queue IO operations to ensure write order - private ready: Promise; - private model: IBackupFilesModel; + private ready!: Promise; + private model!: IBackupFilesModel; constructor( backupWorkspaceResource: URI, @@ -201,10 +212,10 @@ class BackupFileServiceImpl implements IBackupFileService { initialize(backupWorkspaceResource: URI): void { this.backupWorkspacePath = backupWorkspaceResource; - this.ready = this.init(); + this.ready = this.doInitialize(); } - private init(): Promise { + private doInitialize(): Promise { this.model = new BackupFilesModel(this.fileService); return this.model.resolve(this.backupWorkspacePath); @@ -323,7 +334,7 @@ class BackupFileServiceImpl implements IBackupFileService { return contents.substr(0, newLineIndex); } - throw new Error(`Could not find ${JSON.stringify(matchingString)} in first ${maximumBytesToRead} bytes of ${file}`); + throw new Error(`Backup: Could not find ${JSON.stringify(matchingString)} in first ${maximumBytesToRead} bytes of ${file}`); } async resolveBackupContent(backup: URI): Promise> { @@ -357,9 +368,7 @@ class BackupFileServiceImpl implements IBackupFileService { const content = await this.fileService.readFileStream(backup); const factory = await createTextBufferFactoryFromStream(content.value, metaPreambleFilter); - // Trigger read for meta data extraction from the filter above - factory.getFirstLineText(1); - + // Extract meta data (if any) let meta: T | undefined; const metaStartIndex = metaRaw.indexOf(BackupFileServiceImpl.PREAMBLE_META_SEPARATOR); if (metaStartIndex !== -1) { @@ -370,6 +379,15 @@ class BackupFileServiceImpl implements IBackupFileService { } } + // We have seen reports (e.g. https://github.com/microsoft/vscode/issues/78500) where + // if VSCode goes down while writing the backup file, the file can turn empty because + // it always first gets truncated and then written to. In this case, we will not find + // the meta-end marker ('\n') and as such the backup can only be invalid. We bail out + // here if that is the case. + if (!metaEndFound) { + throw new Error(`Backup: Could not find meta end marker in ${backup}. The file is probably corrupt.`); + } + return { value: factory, meta }; } @@ -380,7 +398,7 @@ class BackupFileServiceImpl implements IBackupFileService { export class InMemoryBackupFileService implements IBackupFileService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private backups: Map = new Map(); @@ -440,4 +458,4 @@ export class InMemoryBackupFileService implements IBackupFileService { toBackupResource(resource: URI): URI { return URI.file(join(resource.scheme, this.hashPath(resource))); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/backup/node/backupFileService.ts b/src/vs/workbench/services/backup/node/backupFileService.ts index 1be121ab01..86fa60b3ac 100644 --- a/src/vs/workbench/services/backup/node/backupFileService.ts +++ b/src/vs/workbench/services/backup/node/backupFileService.ts @@ -7,13 +7,14 @@ import { BackupFileService as CommonBackupFileService } from 'vs/workbench/servi import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import * as crypto from 'crypto'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; export class BackupFileService extends CommonBackupFileService { protected hashPath(resource: URI): string { return hashPath(resource); } - } /* @@ -21,5 +22,8 @@ export class BackupFileService extends CommonBackupFileService { */ export function hashPath(resource: URI): string { const str = resource.scheme === Schemas.file || resource.scheme === Schemas.untitled ? resource.fsPath : resource.toString(); + return crypto.createHash('md5').update(str).digest('hex'); -} \ No newline at end of file +} + +registerSingleton(IBackupFileService, BackupFileService); diff --git a/src/vs/workbench/services/backup/test/node/backupFileService.test.ts b/src/vs/workbench/services/backup/test/node/backupFileService.test.ts index 51e799ae62..c69d9c2dc2 100644 --- a/src/vs/workbench/services/backup/test/node/backupFileService.test.ts +++ b/src/vs/workbench/services/backup/test/node/backupFileService.test.ts @@ -27,6 +27,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { hashPath, BackupFileService } from 'vs/workbench/services/backup/node/backupFileService'; import { BACKUPS } from 'vs/platform/environment/common/environment'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; +import { VSBuffer } from 'vs/base/common/buffer'; const userdataDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backupfileservice'); const appSettingsHome = path.join(userdataDir, 'User'); @@ -479,6 +480,28 @@ suite('BackupFileService', () => { await testResolveBackup(fooBarFile, contents, meta, null); }); + test('should throw an error when restoring invalid backup', async () => { + const contents = 'test\nand more stuff'; + + await service.backupResource(fooBarFile, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).createSnapshot(false), 1); + + const backup = await service.loadBackupResource(fooBarFile); + if (!backup) { + throw new Error('Unexpected missing backup'); + } + + await service.fileService.writeFile(backup, VSBuffer.fromString('')); + + let err: Error; + try { + await service.resolveBackupContent(backup); + } catch (error) { + err = error; + } + + assert.ok(err!); + }); + async function testResolveBackup(resource: URI, contents: string, meta?: IBackupTestMetaData, expectedMeta?: IBackupTestMetaData | null) { if (typeof expectedMeta === 'undefined') { expectedMeta = meta; diff --git a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts index a1ce7a201d..49122775d5 100644 --- a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts +++ b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts @@ -51,7 +51,7 @@ class ModelEditTask implements IDisposable { protected _edits: IIdentifiedSingleEditOperation[]; private _expectedModelVersionId: number | undefined; - protected _newEol: EndOfLineSequence; + protected _newEol: EndOfLineSequence | undefined; constructor(private readonly _modelReference: IReference) { this._model = this._modelReference.object.textEditorModel; @@ -142,7 +142,7 @@ class BulkEditModel implements IDisposable { private _textModelResolverService: ITextModelService; private _edits = new Map(); private _editor: ICodeEditor | undefined; - private _tasks: ModelEditTask[]; + private _tasks: ModelEditTask[] | undefined; private _progress: IProgress; constructor( @@ -159,7 +159,7 @@ class BulkEditModel implements IDisposable { } dispose(): void { - this._tasks = dispose(this._tasks); + this._tasks = dispose(this._tasks!); } addEdit(edit: ResourceTextEdit): void { @@ -196,7 +196,7 @@ class BulkEditModel implements IDisposable { } value.forEach(edit => task.addEdit(edit)); - this._tasks.push(task); + this._tasks!.push(task); this._progress.report(undefined); }); promises.push(promise); @@ -208,7 +208,7 @@ class BulkEditModel implements IDisposable { } validate(): ValidationResult { - for (const task of this._tasks) { + for (const task of this._tasks!) { const result = task.validate(); if (!result.canApply) { return result; @@ -218,7 +218,7 @@ class BulkEditModel implements IDisposable { } apply(): void { - for (const task of this._tasks) { + for (const task of this._tasks!) { task.apply(); this._progress.report(undefined); } diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index 404feb75ef..34408c4e22 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -133,7 +133,7 @@ export class ConfigurationEditingService { public _serviceBrand: any; private queue: Queue; - private remoteSettingsResource: URI | null; + private remoteSettingsResource: URI | null = null; constructor( @IConfigurationService private readonly configurationService: IConfigurationService, diff --git a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts index c853520074..2a068ce7fa 100644 --- a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts @@ -250,7 +250,7 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR inputOptions.value = info.default; } return this.quickInputService.input(inputOptions).then(resolvedInput => { - return resolvedInput ? resolvedInput : undefined; + return resolvedInput; }); } diff --git a/src/vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts b/src/vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts index 80bedb4bd9..6f7b92eb4a 100644 --- a/src/vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts +++ b/src/vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts @@ -102,8 +102,20 @@ export const inputsSchema: IJSONSchema = { description: nls.localize('JsonSchema.input.command.command', "The command to execute for this input variable.") }, args: { - type: 'object', - description: nls.localize('JsonSchema.input.command.args', "Optional arguments passed to the command.") + oneOf: [ + { + type: 'object', + description: nls.localize('JsonSchema.input.command.args', "Optional arguments passed to the command.") + }, + { + type: 'array', + description: nls.localize('JsonSchema.input.command.args', "Optional arguments passed to the command.") + }, + { + type: 'string', + description: nls.localize('JsonSchema.input.command.args', "Optional arguments passed to the command.") + } + ] } } } diff --git a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts index f568819053..59a6384572 100644 --- a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts @@ -555,7 +555,7 @@ class MockQuickInputService implements IQuickInputService { return Promise.resolve(options ? 'resolved' + options.prompt : 'resolved'); } - backButton: IQuickInputButton; + backButton!: IQuickInputButton; createQuickPick(): IQuickPick { throw new Error('not implemented.'); diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts index 9809ef95a4..96dd9986f1 100644 --- a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -19,6 +19,7 @@ import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; +import { isWeb } from 'vs/base/common/platform'; export class FileDialogService implements IFileDialogService { @@ -90,9 +91,14 @@ export class FileDialogService implements IFileDialogService { return (schema !== Schemas.file) || (setting === true); } - private ensureFileSchema(schema: string): string[] { + private addFileSchemaIfNeeded(schema: string): string[] { + // Include File schema unless the schema is web // Don't allow untitled schema through. - return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]); + if (isWeb) { + return schema === Schemas.untitled ? [Schemas.file] : [schema]; + } else { + return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]); + } } async pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise { @@ -104,7 +110,7 @@ export class FileDialogService implements IFileDialogService { if (this.shouldUseSimplified(schema)) { const title = nls.localize('openFileOrFolder.title', 'Open File Or Folder'); - const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well + const availableFileSystems = this.addFileSchemaIfNeeded(schema); const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); @@ -130,7 +136,7 @@ export class FileDialogService implements IFileDialogService { if (this.shouldUseSimplified(schema)) { const title = nls.localize('openFile.title', 'Open File'); - const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well + const availableFileSystems = this.addFileSchemaIfNeeded(schema); const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); if (uri) { @@ -152,7 +158,7 @@ export class FileDialogService implements IFileDialogService { if (this.shouldUseSimplified(schema)) { const title = nls.localize('openFolder.title', 'Open Folder'); - const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well + const availableFileSystems = this.addFileSchemaIfNeeded(schema); const uri = await this.pickRemoteResource({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); if (uri) { @@ -175,7 +181,7 @@ export class FileDialogService implements IFileDialogService { if (this.shouldUseSimplified(schema)) { const title = nls.localize('openWorkspace.title', 'Open Workspace'); const filters: FileFilter[] = [{ name: nls.localize('filterName.workspace', 'Workspace'), extensions: [WORKSPACE_EXTENSION] }]; - const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well + const availableFileSystems = this.addFileSchemaIfNeeded(schema); const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems }); if (uri) { @@ -192,7 +198,7 @@ export class FileDialogService implements IFileDialogService { const schema = this.getFileSystemSchema(options); if (this.shouldUseSimplified(schema)) { if (!options.availableFileSystems) { - options.availableFileSystems = this.ensureFileSchema(schema); // always allow file as well + options.availableFileSystems = this.addFileSchemaIfNeeded(schema); } options.title = nls.localize('saveFileAs.title', 'Save As'); @@ -221,7 +227,7 @@ export class FileDialogService implements IFileDialogService { const schema = this.getFileSystemSchema(options); if (this.shouldUseSimplified(schema)) { if (!options.availableFileSystems) { - options.availableFileSystems = this.ensureFileSchema(schema); // always allow file as well + options.availableFileSystems = this.addFileSchemaIfNeeded(schema); } return this.saveRemoteResource(options); @@ -239,7 +245,7 @@ export class FileDialogService implements IFileDialogService { const schema = this.getFileSystemSchema(options); if (this.shouldUseSimplified(schema)) { if (!options.availableFileSystems) { - options.availableFileSystems = this.ensureFileSchema(schema); // always allow file as well + options.availableFileSystems = this.addFileSchemaIfNeeded(schema); } const uri = await this.pickRemoteResource(options); diff --git a/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts b/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts index 4289f43321..60fb9e2d1a 100644 --- a/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts @@ -46,24 +46,24 @@ enum UpdateResult { } export class RemoteFileDialog { - private options: IOpenDialogOptions; - private currentFolder: URI; - private filePickBox: IQuickPick; - private hidden: boolean; - private allowFileSelection: boolean; - private allowFolderSelection: boolean; + private options!: IOpenDialogOptions; + private currentFolder!: URI; + private filePickBox!: IQuickPick; + private hidden: boolean = false; + private allowFileSelection: boolean = true; + private allowFolderSelection: boolean = false; private remoteAuthority: string | undefined; - private requiresTrailing: boolean; + private requiresTrailing: boolean = false; private trailing: string | undefined; private scheme: string = REMOTE_HOST_SCHEME; private contextKey: IContextKey; - private userEnteredPathSegment: string; - private autoCompletePathSegment: string; - private activeItem: FileQuickPickItem; - private userHome: URI; + private userEnteredPathSegment: string = ''; + private autoCompletePathSegment: string = ''; + private activeItem: FileQuickPickItem | undefined; + private userHome!: URI; private badPath: string | undefined; - private remoteAgentEnvironment: IRemoteAgentEnvironment | null; - private separator: string; + private remoteAgentEnvironment: IRemoteAgentEnvironment | null | undefined; + private separator: string = '/'; private onBusyChangeEmitter = new Emitter(); private updatingPromise: CancelablePromise | undefined; @@ -762,8 +762,8 @@ export class RemoteFileDialog { private pathAppend(uri: URI, additional: string): string { if ((additional === '..') || (additional === '.')) { - const basePath = this.pathFromUri(uri); - return basePath + this.separator + additional; + const basePath = this.pathFromUri(uri, true); + return basePath + additional; } else { return this.pathFromUri(resources.joinPath(uri, additional)); } diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index dcadeaf6f9..b148745ef7 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -37,7 +37,7 @@ type ICachedEditorInput = ResourceEditorInput | IFileEditorInput | DataUriEditor export class EditorService extends Disposable implements EditorServiceImpl { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private static CACHE: ResourceMap = new ResourceMap(); @@ -60,8 +60,8 @@ export class EditorService extends Disposable implements EditorServiceImpl { private fileInputFactory: IFileInputFactory; private openEditorHandlers: IOpenEditorOverrideHandler[] = []; - private lastActiveEditor: IEditorInput | null; - private lastActiveGroupId: GroupIdentifier; + private lastActiveEditor: IEditorInput | null = null; + private lastActiveGroupId: GroupIdentifier | null = null; constructor( @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @@ -318,7 +318,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { } const textOptions: ITextEditorOptions = options; - if (!!textOptions.selection) { + if (textOptions.selection || textOptions.viewState) { return TextEditorOptions.create(options); } @@ -451,13 +451,14 @@ export class EditorService extends Disposable implements EditorServiceImpl { typedEditors.push(replaceEditorArg as IEditorReplacement); } else { const editor = replaceEditorArg.editor as IResourceEditor; + const replacement = replaceEditorArg.replacement as IResourceEditor; const typedEditor = this.createInput(editor); - const replacementEditor = this.createInput(replaceEditorArg.replacement as IResourceEditor); + const typedReplacement = this.createInput(replacement); typedEditors.push({ editor: typedEditor, - replacement: replacementEditor, - options: this.toOptions(editor.options) + replacement: typedReplacement, + options: this.toOptions(replacement.options) }); } }); @@ -634,7 +635,7 @@ export interface IEditorOpenHandler { * method by providing a IEditorOpenHandler. */ export class DelegatingEditorService extends EditorService { - private editorOpenHandler: IEditorOpenHandler; + private editorOpenHandler: IEditorOpenHandler | undefined; constructor( @IEditorGroupsService editorGroupService: IEditorGroupsService, diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index 1534c8de13..5c6f95e1f2 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -59,7 +59,13 @@ export const enum GroupsArrangement { /** * Size all groups evenly. */ - EVEN + EVEN, + + /** + * Will behave like MINIMIZE_OTHERS if the active + * group is not already maximized and EVEN otherwise + */ + TOGGLE } export interface GroupLayoutArgument { @@ -176,6 +182,11 @@ export interface IEditorGroupsService { */ readonly onDidLayout: Event; + /** + * An event for when the index of a group changes. + */ + readonly onDidGroupIndexChange: Event; + /** * The size of the editor groups area. */ @@ -343,7 +354,7 @@ export const enum GroupChangeKind { /* Group Changes */ GROUP_ACTIVE, - GROUP_LABEL, + GROUP_INDEX, /* Editor Changes */ EDITOR_OPEN, @@ -374,6 +385,14 @@ export interface IEditorGroup { */ readonly id: GroupIdentifier; + /** + * A number that indicates the position of this group in the visual + * order of groups from left to right and top to bottom. The lowest + * index will likely be top-left while the largest index in most + * cases should be bottom-right, but that depends on the grid. + */ + readonly index: number; + /** * A human readable label for the group. This label can change depending * on the layout of all editor groups. Clients should listen on the diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index a60a343135..eacb061e53 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -51,8 +51,8 @@ export class TestEditorControl extends BaseEditor { } export class TestEditorInput extends EditorInput implements IFileEditorInput { - public gotDisposed: boolean; - private fails: boolean; + public gotDisposed = false; + private fails = false; constructor(private resource: URI) { super(); } getTypeId() { return 'testEditorInputForEditorService'; } diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index a09a66a5ce..311df6ea9f 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -32,11 +32,13 @@ export class BrowserWindowConfiguration implements IWindowConfiguration { nodeCachedDataDir?: string; backupPath?: string; + backupWorkspaceResource?: URI; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; - remoteAuthority: string; + remoteAuthority?: string; + connectionToken?: string; zoomLevel?: number; fullscreen?: boolean; @@ -65,7 +67,8 @@ export interface IBrowserWindowConfiguration { } export class BrowserWorkbenchEnvironmentService implements IEnvironmentService { - _serviceBrand: ServiceIdentifier; + + _serviceBrand!: ServiceIdentifier; readonly configuration: IWindowConfiguration = new BrowserWindowConfiguration(); @@ -102,7 +105,7 @@ export class BrowserWorkbenchEnvironmentService implements IEnvironmentService { for (let p of vars) { const pair = p.split('='); if (pair.length >= 2) { - map.set(decodeURIComponent(pair[0]), decodeURIComponent(pair[1])); + map.set(pair[0], decodeURIComponent(pair[1])); } } diff --git a/src/vs/workbench/services/environment/node/environmentService.ts b/src/vs/workbench/services/environment/node/environmentService.ts index d40325d451..26cf9a3f0d 100644 --- a/src/vs/workbench/services/environment/node/environmentService.ts +++ b/src/vs/workbench/services/environment/node/environmentService.ts @@ -10,16 +10,18 @@ import { memoize } from 'vs/base/common/decorators'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { toBackupWorkspaceResource } from 'vs/workbench/services/backup/common/backup'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; export class WorkbenchEnvironmentService extends EnvironmentService implements IWorkbenchEnvironmentService { - _serviceBrand: any; + _serviceBrand!: ServiceIdentifier; constructor( private _configuration: IWindowConfiguration, execPath: string ) { super(_configuration, execPath); + this._configuration.backupWorkspaceResource = this._configuration.backupPath ? toBackupWorkspaceResource(this._configuration.backupPath, this) : undefined; } diff --git a/src/vs/workbench/services/extensionManagement/node/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/node/extensionManagementService.ts index 44eee62a69..e7e13cd1cb 100644 --- a/src/vs/workbench/services/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/node/extensionManagementService.ts @@ -3,12 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ILocalExtension, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { ExtensionManagementService as BaseExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagementService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class ExtensionManagementService extends BaseExtensionManagementService { @@ -32,5 +33,6 @@ export class ExtensionManagementService extends BaseExtensionManagementService { } return Promise.reject('No Servers to Install'); } - } + +registerSingleton(IExtensionManagementService, ExtensionManagementService); diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 0e9007755a..9ffd299f00 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -170,14 +170,10 @@ export abstract class AbstractExtensionService extends Disposable implements IEx this._startExtensionHostProcess(false, Array.from(this._allRequestedActivateEvents.keys())); } - public startExtensionHost(): void { + protected startExtensionHost(): void { this._startExtensionHostProcess(false, Array.from(this._allRequestedActivateEvents.keys())); } - public stopExtensionHost(): void { - this._stopExtensionHostProcess(); - } - public activateByEvent(activationEvent: string): Promise { if (this._installedExtensionsReady.isOpen()) { // Extensions have been scanned and interpreted diff --git a/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts b/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts index 2f11b6102e..7d0e77ccad 100644 --- a/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts @@ -17,9 +17,9 @@ export class ExtensionDescriptionRegistry { public readonly onDidChange = this._onDidChange.event; private _extensionDescriptions: IExtensionDescription[]; - private _extensionsMap: Map; - private _extensionsArr: IExtensionDescription[]; - private _activationMap: Map; + private _extensionsMap!: Map; + private _extensionsArr!: IExtensionDescription[]; + private _activationMap!: Map; constructor(extensionDescriptions: IExtensionDescription[]) { this._extensionDescriptions = extensionDescriptions; diff --git a/src/vs/workbench/services/extensions/node/extensionHostMain.ts b/src/vs/workbench/services/extensions/common/extensionHostMain.ts similarity index 71% rename from src/vs/workbench/services/extensions/node/extensionHostMain.ts rename to src/vs/workbench/services/extensions/common/extensionHostMain.ts index d2d1cf46aa..d93fa9b388 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostMain.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostMain.ts @@ -5,20 +5,23 @@ import { timeout } from 'vs/base/common/async'; import * as errors from 'vs/base/common/errors'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { Counter } from 'vs/base/common/numbers'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI, setUriThrowOnMissingScheme } from 'vs/base/common/uri'; import { IURITransformer } from 'vs/base/common/uriIpc'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { IInitData, MainContext, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; -import { ExtHostExtensionService, IHostUtils } from 'vs/workbench/api/node/extHostExtensionService'; import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService'; -import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { RPCProtocol } from 'vs/workbench/services/extensions/common/rpcProtocol'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { ILogService } from 'vs/platform/log/common/log'; +import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostRpcService, ExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IURITransformerService, URITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; +import { IExtHostExtensionService, IHostUtils } from 'vs/workbench/api/common/extHostExtensionService'; // we don't (yet) throw when extensions parse // uris that have no scheme @@ -40,11 +43,8 @@ export class ExtensionHostMain { private _isTerminating: boolean; private readonly _hostUtils: IHostUtils; - private readonly _extensionService: ExtHostExtensionService; - private readonly _extHostLogService: ExtHostLogService; - private disposables: IDisposable[] = []; - - private _searchRequestIdProvider: Counter; + private readonly _extensionService: IExtHostExtensionService; + private readonly _disposables = new DisposableStore(); constructor( protocol: IMessagePassingProtocol, @@ -59,32 +59,31 @@ export class ExtensionHostMain { const rpcProtocol = new RPCProtocol(protocol, null, uriTransformer); // ensure URIs are transformed and revived - initData = this.transform(initData, rpcProtocol); + initData = ExtensionHostMain._transform(initData, rpcProtocol); // allow to patch console consolePatchFn(rpcProtocol.getProxy(MainContext.MainThreadConsole)); // services - this._extHostLogService = new ExtHostLogService(logServiceFn(initData), initData.logsLocation.fsPath); - this.disposables.push(this._extHostLogService); + const extHostLogService = new ExtHostLogService(logServiceFn(initData), initData.logsLocation.fsPath); + this._disposables.add(extHostLogService); - this._searchRequestIdProvider = new Counter(); - const extHostWorkspace = new ExtHostWorkspace(rpcProtocol, this._extHostLogService, this._searchRequestIdProvider, withNullAsUndefined(initData.workspace)); + // bootstrap services + const services = new ServiceCollection(...getSingletonServiceDescriptors()); + services.set(IExtHostInitDataService, { _serviceBrand: undefined, ...initData }); + services.set(IExtHostRpcService, new ExtHostRpcService(rpcProtocol)); + services.set(ILogService, extHostLogService); + services.set(IURITransformerService, new URITransformerService(uriTransformer)); + services.set(IHostUtils, hostUtils); - this._extHostLogService.info('extension host started'); - this._extHostLogService.trace('initData', initData); + const instaService: IInstantiationService = new InstantiationService(services, true); - const extHostConfiguraiton = new ExtHostConfiguration(rpcProtocol.getProxy(MainContext.MainThreadConfiguration), extHostWorkspace); - this._extensionService = new ExtHostExtensionService( - hostUtils, - initData, - rpcProtocol, - extHostWorkspace, - extHostConfiguraiton, - initData.environment, - this._extHostLogService, - uriTransformer - ); + extHostLogService.info('extension host started'); + extHostLogService.trace('initData', initData); + + // todo@joh -> not soo nice... + this._extensionService = instaService.invokeFunction(accessor => accessor.get(IExtHostExtensionService)); + this._extensionService.initialize(); // error forwarding and stack trace scanning Error.stackTraceLimit = 100; // increase number of stack frames (from 10, https://github.com/v8/v8/wiki/Stack-Trace-API) @@ -127,7 +126,7 @@ export class ExtensionHostMain { } this._isTerminating = true; - this.disposables = dispose(this.disposables); + this._disposables.dispose(); errors.setUnexpectedErrorHandler((err) => { // TODO: write to log once we have one @@ -141,7 +140,7 @@ export class ExtensionHostMain { }, 1000); } - private transform(initData: IInitData, rpcProtocol: RPCProtocol): IInitData { + private static _transform(initData: IInitData, rpcProtocol: RPCProtocol): IInitData { initData.extensions.forEach((ext) => (ext).extensionLocation = URI.revive(rpcProtocol.transformIncomingURIs(ext.extensionLocation))); initData.environment.appRoot = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appRoot)); initData.environment.appSettingsHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appSettingsHome)); diff --git a/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts b/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts index 9f8bda8d3d..524a4d4056 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts @@ -154,9 +154,13 @@ export class ExtensionHostProcessManager extends Disposable { private async _measureUp(proxy: ExtHostExtensionServiceShape): Promise { const SIZE = 10 * 1024 * 1024; // 10MB - let b = Buffer.alloc(SIZE, Math.random() % 256); + let buff = VSBuffer.alloc(SIZE); + let value = Math.ceil(Math.random() * 256); + for (let i = 0; i < buff.byteLength; i++) { + buff.writeUInt8(i, value); + } const sw = StopWatch.create(true); - await proxy.$test_up(VSBuffer.wrap(b)); + await proxy.$test_up(buff); sw.stop(); return ExtensionHostProcessManager._convert(SIZE, sw.elapsed()); } diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index b6af9700c0..515025e22b 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -219,16 +219,6 @@ export interface IExtensionService { */ restartExtensionHost(): void; - /** - * Starts the extension host. - */ - startExtensionHost(): void; - - /** - * Stops the extension host. - */ - stopExtensionHost(): void; - /** * Modify the environment of the remote extension host * @param env New properties for the remote extension host @@ -282,8 +272,6 @@ export class NullExtensionService implements IExtensionService { getExtensionsStatus(): { [id: string]: IExtensionsStatus; } { return Object.create(null); } getInspectPort(): number { return 0; } restartExtensionHost(): void { } - startExtensionHost(): void { } - stopExtensionHost(): void { } async setRemoteEnvironment(_env: { [key: string]: string | null }): Promise { } canAddExtension(): boolean { return false; } canRemoveExtension(): boolean { return false; } diff --git a/src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts b/src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts index 929c2fa347..49cd0b17e1 100644 --- a/src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts @@ -20,6 +20,9 @@ import { IWindowService } from 'vs/platform/windows/common/windows'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContribution, Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; const FIVE_MINUTES = 5 * 60 * 1000; const THIRTY_SECONDS = 30 * 1000; @@ -48,7 +51,7 @@ export interface IExtensionUrlHandler { * * It also makes sure the user confirms opening URLs directed towards extensions. */ -export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { +class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { readonly _serviceBrand: any; @@ -67,7 +70,6 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @IStorageService private readonly storageService: IStorageService, @IConfigurationService private readonly configurationService: IConfigurationService - ) { const interval = setInterval(() => this.garbageCollect(), THIRTY_SECONDS); const urlToHandleValue = this.storageService.get(URL_TO_HANDLE, StorageScope.WORKSPACE); @@ -80,6 +82,9 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { urlService.registerHandler(this), toDisposable(() => clearInterval(interval)) ); + + const cache = ExtensionUrlBootstrapHandler.cache; + setTimeout(() => cache.forEach(uri => this.handleURL(uri))); } async handleURL(uri: URI, confirmed?: boolean): Promise { @@ -333,3 +338,33 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { } registerSingleton(IExtensionUrlHandler, ExtensionUrlHandler); + +/** + * This class handles URLs before `ExtensionUrlHandler` is instantiated. + * More info: https://github.com/microsoft/vscode/issues/73101 + */ +class ExtensionUrlBootstrapHandler implements IWorkbenchContribution, IURLHandler { + + private static _cache: URI[] = []; + private static disposable: IDisposable; + + static get cache(): URI[] { + ExtensionUrlBootstrapHandler.disposable.dispose(); + + const result = ExtensionUrlBootstrapHandler._cache; + ExtensionUrlBootstrapHandler._cache = []; + return result; + } + + constructor(@IURLService urlService: IURLService) { + ExtensionUrlBootstrapHandler.disposable = urlService.registerHandler(this); + } + + handleURL(uri: URI): Promise { + ExtensionUrlBootstrapHandler._cache.push(uri); + return Promise.resolve(true); + } +} + +const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchRegistry.registerWorkbenchContribution(ExtensionUrlBootstrapHandler, LifecyclePhase.Ready); diff --git a/src/vs/workbench/services/extensions/common/proxyIdentifier.ts b/src/vs/workbench/services/extensions/common/proxyIdentifier.ts index 3489e05b35..9e6d36f48f 100644 --- a/src/vs/workbench/services/extensions/common/proxyIdentifier.ts +++ b/src/vs/workbench/services/extensions/common/proxyIdentifier.ts @@ -20,10 +20,10 @@ export interface IRPCProtocol { assertRegistered(identifiers: ProxyIdentifier[]): void; } +// @ts-ignore export class ProxyIdentifier { public static count = 0; _proxyIdentifierBrand: void; - _suppressCompilerUnusedWarning: T; public readonly isMain: boolean; public readonly sid: string; diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts index c249bcb8a2..80a0df64f3 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts @@ -183,6 +183,7 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH const r: IInitData = { commit: this._productService.commit, version: this._productService.version, + vscodeVersion: this._productService.vscodeVersion, // {{SQL CARBON EDIT}} add vscode version parentPid: remoteExtensionHostData.pid, environment: { isExtensionDevelopmentDebug, diff --git a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts index 74d7f936f4..d7a847e09b 100644 --- a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts +++ b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts @@ -48,8 +48,8 @@ function getExtraDevSystemExtensionsRoot(): string { export class CachedExtensionScanner { public readonly scannedExtensions: Promise; - private _scannedExtensionsResolve: (result: IExtensionDescription[]) => void; - private _scannedExtensionsReject: (err: any) => void; + private _scannedExtensionsResolve!: (result: IExtensionDescription[]) => void; + private _scannedExtensionsReject!: (err: any) => void; public readonly translationConfig: Promise; constructor( diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 78d569856c..8a02af3982 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -87,6 +87,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { this._terminating = false; this._namedPipeServer = null; + this._inspectPort = null; this._extensionHostProcess = null; this._extensionHostConnection = null; this._messageProtocol = null; @@ -388,6 +389,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { const r: IInitData = { commit: product.commit, version: pkg.version, + vscodeVersion: product.vscodeVersion, // {{SQL CARBON EDIT}} add vscode version parentPid: process.pid, environment: { isExtensionDevelopmentDebug: this._isExtensionDevDebug, diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index 0a35b8604c..80778382ef 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -5,7 +5,7 @@ import * as nativeWatchdog from 'native-watchdog'; import * as net from 'net'; -import * as minimist from 'minimist'; +import * as minimist from 'vscode-minimist'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Event, Emitter } from 'vs/base/common/event'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; @@ -14,14 +14,15 @@ import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net' import product from 'vs/platform/product/node/product'; import { IInitData, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol'; import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessage, IExtHostReadyMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; -import { ExtensionHostMain, IExitFn, ILogServiceFn } from 'vs/workbench/services/extensions/node/extensionHostMain'; +import { ExtensionHostMain, IExitFn, ILogServiceFn } from 'vs/workbench/services/extensions/common/extensionHostMain'; import { VSBuffer } from 'vs/base/common/buffer'; import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions'; import { IURITransformer, URITransformer, IRawURITransformer } from 'vs/base/common/uriIpc'; import { exists } from 'vs/base/node/pfs'; import { realpath } from 'vs/base/node/extpath'; -import { IHostUtils } from 'vs/workbench/api/node/extHostExtensionService'; +import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService'; import { SpdLogService } from 'vs/platform/log/node/spdlogService'; +import 'vs/workbench/api/node/extHost.services'; interface ParsedExtHostArgs { uriTransformerPath?: string; @@ -312,6 +313,7 @@ export async function startExtensionHostProcess(): Promise { // host abstraction const hostUtils = new class NodeHost implements IHostUtils { + _serviceBrand: undefined; exit(code: number) { nativeExit(code); } exists(path: string) { return exists(path); } realpath(path: string) { return realpath(path); } diff --git a/src/vs/workbench/services/extensions/node/extensionPoints.ts b/src/vs/workbench/services/extensions/node/extensionPoints.ts index 4ba29a31a3..e56f942196 100644 --- a/src/vs/workbench/services/extensions/node/extensionPoints.ts +++ b/src/vs/workbench/services/extensions/node/extensionPoints.ts @@ -413,7 +413,7 @@ class ExtensionManifestValidator extends ExtensionManifestHandler { export class ExtensionScannerInput { - public mtime: number; + public mtime: number | undefined; constructor( public readonly ourVersion: string, @@ -613,4 +613,4 @@ export class ExtensionScanner { return resultArr; }); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/extensions/test/node/rpcProtocol.test.ts b/src/vs/workbench/services/extensions/test/node/rpcProtocol.test.ts index 1bab0adf75..439a776fa9 100644 --- a/src/vs/workbench/services/extensions/test/node/rpcProtocol.test.ts +++ b/src/vs/workbench/services/extensions/test/node/rpcProtocol.test.ts @@ -14,7 +14,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; suite('RPCProtocol', () => { class MessagePassingProtocol implements IMessagePassingProtocol { - private _pair: MessagePassingProtocol; + private _pair?: MessagePassingProtocol; private readonly _onMessage = new Emitter(); public readonly onMessage: Event = this._onMessage.event; @@ -25,7 +25,7 @@ suite('RPCProtocol', () => { public send(buffer: VSBuffer): void { process.nextTick(() => { - this._pair._onMessage.fire(buffer); + this._pair!._onMessage.fire(buffer); }); } } diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index a66b46e151..ac4ee5e312 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -99,7 +99,7 @@ interface IRecentlyClosedFile { export class HistoryService extends Disposable implements IHistoryService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private static readonly STORAGE_KEY = 'history.entries'; private static readonly MAX_HISTORY_ITEMS = 200; @@ -115,10 +115,10 @@ export class HistoryService extends Disposable implements IHistoryService { private stack: IStackEntry[]; private index: number; private lastIndex: number; - private navigatingInStack: boolean; - private currentTextEditorState: TextEditorState | null; + private navigatingInStack = false; + private currentTextEditorState: TextEditorState | null = null; - private lastEditLocation: IStackEntry; + private lastEditLocation: IStackEntry | undefined; private history: Array; private recentlyClosedFiles: IRecentlyClosedFile[]; diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 925a6a6537..fc85ce0a2a 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -725,7 +725,6 @@ const keyboardConfiguration: IConfigurationNode = { 'markdownDescription': nls.localize('dispatch', "Controls the dispatching logic for key presses to use either `code` (recommended) or `keyCode`."), 'included': OS === OperatingSystem.Macintosh || OS === OperatingSystem.Linux } - // no touch bar support } }; diff --git a/src/vs/workbench/services/keybinding/electron-browser/keybinding.contribution.ts b/src/vs/workbench/services/keybinding/electron-browser/keybinding.contribution.ts index f0fd8c00aa..c851126a7f 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/keybinding.contribution.ts +++ b/src/vs/workbench/services/keybinding/electron-browser/keybinding.contribution.ts @@ -22,8 +22,17 @@ const keyboardConfiguration: IConfigurationNode = { 'default': true, 'description': nls.localize('touchbar.enabled', "Enables the macOS touchbar buttons on the keyboard if available."), 'included': OS === OperatingSystem.Macintosh && parseFloat(release()) >= 16 // Minimum: macOS Sierra (10.12.x = darwin 16.x) + }, + 'keyboard.touchbar.ignored': { + 'type': 'array', + 'items': { + 'type': 'string' + }, + 'default': [], + 'description': nls.localize('touchbar.ignored', 'A set of identifiers for entries in the touchbar that should not show up (for example `workbench.action.navigateBack`.'), + 'included': OS === OperatingSystem.Macintosh && parseFloat(release()) >= 16 // Minimum: macOS Sierra (10.12.x = darwin 16.x) } } }; -configurationRegistry.registerConfiguration(keyboardConfiguration); \ No newline at end of file +configurationRegistry.registerConfiguration(keyboardConfiguration); diff --git a/src/vs/workbench/services/keybinding/test/browserKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/browserKeyboardMapper.test.ts index 3133701aed..064ad120cb 100644 --- a/src/vs/workbench/services/keybinding/test/browserKeyboardMapper.test.ts +++ b/src/vs/workbench/services/keybinding/test/browserKeyboardMapper.test.ts @@ -40,12 +40,12 @@ suite('keyboard layout loader', () => { let commandService = instantiationService.stub(ICommandService, {}); let instance = new TestKeyboardMapperFactory(notitifcationService, storageService, commandService); - test('load default US keyboard layout', () => { + test.skip('load default US keyboard layout', () => { assert.notEqual(instance.activeKeyboardLayout, null); assert.equal(instance.activeKeyboardLayout!.isUSStandard, true); }); - test('isKeyMappingActive', () => { + test.skip('isKeyMappingActive', () => { assert.equal(instance.isKeyMappingActive({ KeyA: { value: 'a', diff --git a/src/vs/workbench/services/mode/common/workbenchModeService.ts b/src/vs/workbench/services/mode/common/workbenchModeService.ts index b7e1afb606..2cc3f1e141 100644 --- a/src/vs/workbench/services/mode/common/workbenchModeService.ts +++ b/src/vs/workbench/services/mode/common/workbenchModeService.ts @@ -94,7 +94,7 @@ export const languagesExtPoint: IExtensionPoint = export class WorkbenchModeServiceImpl extends ModeServiceImpl { private _configurationService: IConfigurationService; private _extensionService: IExtensionService; - private _onReadyPromise: Promise; + private _onReadyPromise: Promise | undefined; constructor( @IExtensionService extensionService: IExtensionService, @@ -232,4 +232,4 @@ function isValidLanguageExtensionPoint(value: IRawLanguageExtensionPoint, collec return true; } -registerSingleton(IModeService, WorkbenchModeServiceImpl); \ No newline at end of file +registerSingleton(IModeService, WorkbenchModeServiceImpl); diff --git a/src/vs/workbench/services/notification/common/notificationService.ts b/src/vs/workbench/services/notification/common/notificationService.ts index f477cb7281..94f98e5efd 100644 --- a/src/vs/workbench/services/notification/common/notificationService.ts +++ b/src/vs/workbench/services/notification/common/notificationService.ts @@ -3,17 +3,19 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { INotificationService, INotification, INotificationHandle, Severity, NotificationMessage, INotificationActions, IPromptChoice, IPromptOptions, IStatusMessageOptions } from 'vs/platform/notification/common/notification'; +import * as nls from 'vs/nls'; +import { INotificationService, INotification, INotificationHandle, Severity, NotificationMessage, INotificationActions, IPromptChoice, IPromptOptions, IStatusMessageOptions, NoOpNotification } from 'vs/platform/notification/common/notification'; import { INotificationsModel, NotificationsModel, ChoiceAction } from 'vs/workbench/common/notifications'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, Action } from 'vs/base/common/actions'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; export class NotificationService extends Disposable implements INotificationService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private _model: INotificationsModel = this._register(new NotificationsModel()); @@ -21,6 +23,10 @@ export class NotificationService extends Disposable implements INotificationServ return this._model; } + constructor(@IStorageService private readonly storageService: IStorageService) { + super(); + } + info(message: NotificationMessage | NotificationMessage[]): void { if (Array.isArray(message)) { message.forEach(m => this.info(m)); @@ -52,12 +58,80 @@ export class NotificationService extends Disposable implements INotificationServ } notify(notification: INotification): INotificationHandle { - return this.model.addNotification(notification); + const toDispose = new DisposableStore(); + + // Handle neverShowAgain option accordingly + let handle: INotificationHandle; + if (notification.neverShowAgain) { + + // If the user already picked to not show the notification + // again, we return with a no-op notification here + const id = notification.neverShowAgain.id; + if (this.storageService.getBoolean(id, StorageScope.GLOBAL)) { + return new NoOpNotification(); + } + + const neverShowAgainAction = toDispose.add(new Action( + 'workbench.notification.neverShowAgain', + nls.localize('neverShowAgain', "Don't Show Again"), + undefined, true, () => { + + // Close notification + handle.close(); + + // Remember choice + this.storageService.store(id, true, StorageScope.GLOBAL); + + return Promise.resolve(); + })); + + // Insert as primary or secondary action + const actions = notification.actions || { primary: [], secondary: [] }; + if (!notification.neverShowAgain.isSecondary) { + actions.primary = [neverShowAgainAction, ...(actions.primary || [])]; // action comes first + } else { + actions.secondary = [...(actions.secondary || []), neverShowAgainAction]; // actions comes last + } + + notification.actions = actions; + } + + // Show notification + handle = this.model.addNotification(notification); + + // Cleanup when notification gets disposed + Event.once(handle.onDidClose)(() => toDispose.dispose()); + + return handle; } prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions): INotificationHandle { const toDispose = new DisposableStore(); + // Handle neverShowAgain option accordingly + if (options && options.neverShowAgain) { + + // If the user already picked to not show the notification + // again, we return with a no-op notification here + const id = options.neverShowAgain.id; + if (this.storageService.getBoolean(id, StorageScope.GLOBAL)) { + return new NoOpNotification(); + } + + const neverShowAgainChoice = { + label: nls.localize('neverShowAgain', "Don't Show Again"), + run: () => this.storageService.store(id, true, StorageScope.GLOBAL), + isSecondary: options.neverShowAgain.isSecondary + }; + + // Insert as primary or secondary action + if (!options.neverShowAgain.isSecondary) { + choices = [neverShowAgainChoice, ...choices]; // action comes first + } else { + choices = [...choices, neverShowAgainChoice]; // actions comes last + } + } + let choiceClicked = false; let handle: INotificationHandle; @@ -108,4 +182,4 @@ export class NotificationService extends Disposable implements INotificationServ } } -registerSingleton(INotificationService, NotificationService, true); \ No newline at end of file +registerSingleton(INotificationService, NotificationService, true); diff --git a/src/vs/workbench/services/output/common/outputChannelModel.ts b/src/vs/workbench/services/output/common/outputChannelModel.ts index 49dc1b0f95..fe74ffb546 100644 --- a/src/vs/workbench/services/output/common/outputChannelModel.ts +++ b/src/vs/workbench/services/output/common/outputChannelModel.ts @@ -58,7 +58,7 @@ export abstract class AbstractFileOutputChannelModel extends Disposable implemen readonly onDispose: Event = this._onDispose.event; protected modelUpdater: RunOnceScheduler; - protected model: ITextModel | null; + protected model: ITextModel | null = null; protected startOffset: number = 0; protected endOffset: number = 0; @@ -288,7 +288,7 @@ export class BufferredOutputChannel extends Disposable implements IOutputChannel readonly onDispose: Event = this._onDispose.event; private modelUpdater: RunOnceScheduler; - private model: ITextModel | null; + private model: ITextModel | null = null; private readonly bufferredContent: BufferedContent; private lastReadId: number | undefined = undefined; diff --git a/src/vs/workbench/services/output/node/outputChannelModelService.ts b/src/vs/workbench/services/output/node/outputChannelModelService.ts index b297aa5881..59e17d0c52 100644 --- a/src/vs/workbench/services/output/node/outputChannelModelService.ts +++ b/src/vs/workbench/services/output/node/outputChannelModelService.ts @@ -213,7 +213,7 @@ export class OutputChannelModelService extends AsbtractOutputChannelModelService this.instantiationService.createInstance(DelegatedOutputChannelModel, id, modelUri, mimeType, this.outputDir); } - private _outputDir: Promise | null; + private _outputDir: Promise | null = null; private get outputDir(): Promise { if (!this._outputDir) { const outputDir = URI.file(join(this.environmentService.logsPath, `output_${this.environmentService.configuration.windowId}_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`)); diff --git a/src/vs/workbench/services/progress/browser/editorProgressService.ts b/src/vs/workbench/services/progress/browser/editorProgressService.ts index 693be8fe64..931cb44f60 100644 --- a/src/vs/workbench/services/progress/browser/editorProgressService.ts +++ b/src/vs/workbench/services/progress/browser/editorProgressService.ts @@ -9,5 +9,5 @@ import { ProgressBarIndicator } from 'vs/workbench/services/progress/browser/pro export class EditorProgressService extends ProgressBarIndicator { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; } diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index efd7602c15..5172a1a44f 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -28,7 +28,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; export class ProgressService extends Disposable implements IProgressService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private readonly stack: [IProgressOptions, Progress][] = []; private readonly globalStatusEntry = this._register(new MutableDisposable()); diff --git a/src/vs/workbench/services/progress/test/progressIndicator.test.ts b/src/vs/workbench/services/progress/test/progressIndicator.test.ts index 14109b1b8f..c498244f72 100644 --- a/src/vs/workbench/services/progress/test/progressIndicator.test.ts +++ b/src/vs/workbench/services/progress/test/progressIndicator.test.ts @@ -28,7 +28,7 @@ class TestViewlet implements IViewlet { } class TestCompositeScope extends CompositeScope { - isActive: boolean; + isActive: boolean = false; constructor(viewletService: IViewletService, panelService: IPanelService, scopeId: string) { super(viewletService, panelService, scopeId); @@ -39,10 +39,10 @@ class TestCompositeScope extends CompositeScope { } class TestProgressBar { - fTotal: number; - fWorked: number; - fInfinite: boolean; - fDone: boolean; + fTotal: number = 0; + fWorked: number = 0; + fInfinite: boolean = false; + fDone: boolean = false; constructor() { } diff --git a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts index 8cc8f15dbd..1d7c128de3 100644 --- a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts @@ -31,6 +31,7 @@ export abstract class AbstractRemoteAgentService extends Disposable { @IEnvironmentService protected readonly _environmentService: IEnvironmentService ) { super(); + this._environment = null; } abstract getConnection(): IRemoteAgentConnection | null; diff --git a/src/vs/workbench/services/remote/node/tunnelService.ts b/src/vs/workbench/services/remote/node/tunnelService.ts index c454cc80cc..093c7d4f06 100644 --- a/src/vs/workbench/services/remote/node/tunnelService.ts +++ b/src/vs/workbench/services/remote/node/tunnelService.ts @@ -14,6 +14,7 @@ import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remot import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory'; import { ISignService } from 'vs/platform/sign/common/sign'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export async function createRemoteTunnel(options: IConnectionOptions, tunnelRemotePort: number): Promise { const tunnel = new NodeRemoteTunnel(options, tunnelRemotePort); @@ -113,3 +114,5 @@ export class TunnelService implements ITunnelService { return createRemoteTunnel(options, remotePort); } } + +registerSingleton(ITunnelService, TunnelService, true); diff --git a/src/vs/workbench/services/request/browser/requestService.ts b/src/vs/workbench/services/request/browser/requestService.ts index 20b2b7b2b6..79bdbf4fe5 100644 --- a/src/vs/workbench/services/request/browser/requestService.ts +++ b/src/vs/workbench/services/request/browser/requestService.ts @@ -16,7 +16,6 @@ export class RequestService extends BrowserRequestService { private readonly remoteRequestChannel: RequestChannelClient | null; constructor( - private readonly requestHandler: ((options: IRequestOptions) => Promise) | undefined, @IRemoteAgentService remoteAgentService: IRemoteAgentService, @IConfigurationService configurationService: IConfigurationService, @ILogService logService: ILogService @@ -27,9 +26,6 @@ export class RequestService extends BrowserRequestService { } async request(options: IRequestOptions, token: CancellationToken): Promise { - if (this.requestHandler) { - return this.requestHandler(options); - } try { const context = await super.request(options, token); if (this.remoteRequestChannel && context.res.statusCode === 405) { @@ -45,4 +41,4 @@ export class RequestService extends BrowserRequestService { } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index ccd5af2f73..959f088658 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -251,20 +251,23 @@ export class TextSearchMatch implements ITextSearchMatch { constructor(text: string, range: ISearchRange | ISearchRange[], previewOptions?: ITextSearchPreviewOptions) { this.ranges = range; - if (previewOptions && previewOptions.matchLines === 1 && !Array.isArray(range)) { + if (previewOptions && previewOptions.matchLines === 1 && (!Array.isArray(range) || range.length === 1)) { + const oneRange = Array.isArray(range) ? range[0] : range; + // 1 line preview requested text = getNLines(text, previewOptions.matchLines); const leadingChars = Math.floor(previewOptions.charsPerLine / 5); - const previewStart = Math.max(range.startColumn - leadingChars, 0); + const previewStart = Math.max(oneRange.startColumn - leadingChars, 0); const previewText = text.substring(previewStart, previewOptions.charsPerLine + previewStart); - const endColInPreview = (range.endLineNumber - range.startLineNumber + 1) <= previewOptions.matchLines ? - Math.min(previewText.length, range.endColumn - previewStart) : // if number of match lines will not be trimmed by previewOptions + const endColInPreview = (oneRange.endLineNumber - oneRange.startLineNumber + 1) <= previewOptions.matchLines ? + Math.min(previewText.length, oneRange.endColumn - previewStart) : // if number of match lines will not be trimmed by previewOptions previewText.length; // if number of lines is trimmed + const oneLineRange = new OneLineRange(0, oneRange.startColumn - previewStart, endColInPreview); this.preview = { text: previewText, - matches: new OneLineRange(0, range.startColumn - previewStart, endColInPreview) + matches: Array.isArray(range) ? [oneLineRange] : oneLineRange }; } else { const firstMatchLine = Array.isArray(range) ? range[0].startLineNumber : range.startLineNumber; diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index 1279e20e9a..cabe361a30 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -21,9 +21,11 @@ import { deserializeSearchError, FileMatch, ICachedSearchStats, IFileMatch, IFil import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; export class SearchService extends Disposable implements ISearchService { - _serviceBrand: any; + + _serviceBrand!: ServiceIdentifier; protected diskSearch: ISearchResultProvider; private readonly fileSearchProviders = new Map(); diff --git a/src/vs/workbench/services/telemetry/browser/telemetryService.ts b/src/vs/workbench/services/telemetry/browser/telemetryService.ts index 27c2c51c7e..fb15d82c28 100644 --- a/src/vs/workbench/services/telemetry/browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/browser/telemetryService.ts @@ -15,67 +15,10 @@ import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/pla import { IStorageService } from 'vs/platform/storage/common/storage'; import { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/browser/workbenchCommonProperties'; import { IProductService } from 'vs/platform/product/common/product'; - -interface IConfig { - instrumentationKey?: string; - endpointUrl?: string; - emitLineDelimitedJson?: boolean; - accountId?: string; - sessionRenewalMs?: number; - sessionExpirationMs?: number; - maxBatchSizeInBytes?: number; - maxBatchInterval?: number; - enableDebug?: boolean; - disableExceptionTracking?: boolean; - disableTelemetry?: boolean; - verboseLogging?: boolean; - diagnosticLogInterval?: number; - samplingPercentage?: number; - autoTrackPageVisitTime?: boolean; - disableAjaxTracking?: boolean; - overridePageViewDuration?: boolean; - maxAjaxCallsPerView?: number; - disableDataLossAnalysis?: boolean; - disableCorrelationHeaders?: boolean; - correlationHeaderExcludedDomains?: string[]; - disableFlushOnBeforeUnload?: boolean; - enableSessionStorageBuffer?: boolean; - isCookieUseDisabled?: boolean; - cookieDomain?: string; - isRetryDisabled?: boolean; - url?: string; - isStorageUseDisabled?: boolean; - isBeaconApiDisabled?: boolean; - sdkExtension?: string; - isBrowserLinkTrackingEnabled?: boolean; - appId?: string; - enableCorsCorrelation?: boolean; -} - -declare class Microsoft { - public static ApplicationInsights: { - Initialization: { - new(init: { config: IConfig }): AppInsights; - } - }; -} - -declare interface IAppInsightsClient { - config: IConfig; - - /** Log a user action or other occurrence. */ - trackEvent: (name: string, properties?: { [key: string]: string }, measurements?: { [key: string]: number }) => void; - - /** Immediately send all queued telemetry. Synchronous. */ - flush(): void; -} - -interface AppInsights { - loadAppInsights: () => IAppInsightsClient; -} +import { ApplicationInsights } from '@microsoft/applicationinsights-web'; export class WebTelemetryAppender implements ITelemetryAppender { - private _aiClient?: IAppInsightsClient; + private _aiClient?: ApplicationInsights; constructor(aiKey: string, private _logService: ILogService) { const initConfig = { @@ -89,8 +32,8 @@ export class WebTelemetryAppender implements ITelemetryAppender { } }; - const appInsights = new Microsoft.ApplicationInsights.Initialization(initConfig); - this._aiClient = appInsights.loadAppInsights(); + this._aiClient = new ApplicationInsights(initConfig); + this._aiClient.loadAppInsights(); } log(eventName: string, data: any): void { @@ -101,7 +44,11 @@ export class WebTelemetryAppender implements ITelemetryAppender { data = validateTelemetryData(data); this._logService.trace(`telemetry/${eventName}`, data); - this._aiClient.trackEvent('monacoworkbench/' + eventName, data.properties, data.measurements); + this._aiClient.trackEvent({ + name: 'monacoworkbench/' + eventName, + properties: data.properties, + measurements: data.measurements + }); } flush(): Promise | undefined { @@ -167,4 +114,4 @@ export class TelemetryService extends Disposable implements ITelemetryService { } } -registerSingleton(ITelemetryService, TelemetryService); \ No newline at end of file +registerSingleton(ITelemetryService, TelemetryService); diff --git a/src/vs/workbench/services/textMate/common/TMScopeRegistry.ts b/src/vs/workbench/services/textMate/common/TMScopeRegistry.ts index cb711b40ab..f0767618d7 100644 --- a/src/vs/workbench/services/textMate/common/TMScopeRegistry.ts +++ b/src/vs/workbench/services/textMate/common/TMScopeRegistry.ts @@ -31,7 +31,7 @@ export class TMScopeRegistry extends Disposable { constructor() { super(); - this.reset(); + this._scopeNameToLanguageRegistration = Object.create(null); } public reset(): void { diff --git a/src/vs/workbench/services/textMate/electron-browser/textMateWorker.ts b/src/vs/workbench/services/textMate/electron-browser/textMateWorker.ts index 27dc6111a8..481371dc39 100644 --- a/src/vs/workbench/services/textMate/electron-browser/textMateWorker.ts +++ b/src/vs/workbench/services/textMate/electron-browser/textMateWorker.ts @@ -80,7 +80,7 @@ class TextMateWorkerModel extends MirrorTextModel { const languageId = this._languageId; this._worker.getOrCreateGrammar(languageId).then((r) => { - if (this._isDisposed || languageId !== this._languageId) { + if (this._isDisposed || languageId !== this._languageId || !r) { return; } @@ -118,7 +118,7 @@ export class TextMateWorker { private readonly _host: TextMateWorkerHost; private readonly _models: { [uri: string]: TextMateWorkerModel; }; private readonly _grammarCache: Promise[]; - private readonly _grammarFactory: TMGrammarFactory; + private readonly _grammarFactory: TMGrammarFactory | null; constructor(ctx: IWorkerContext, createData: ICreateData) { this._host = ctx.host; @@ -135,23 +135,23 @@ export class TextMateWorker { }; }); - let vscodeTextmate: typeof import('vscode-textmate'); const globalDefine = (self).define; try { (self).define.amd = undefined; - vscodeTextmate = require.__$__nodeRequire('vscode-textmate'); + const vscodeTextmate = require.__$__nodeRequire('vscode-textmate'); + + this._grammarFactory = new TMGrammarFactory({ + logTrace: (msg: string) => {/* console.log(msg) */ }, + logError: (msg: string, err: any) => console.error(msg, err), + readFile: (resource: URI) => this._host.readFile(resource) + }, grammarDefinitions, vscodeTextmate, undefined); } catch (err) { console.error(err); + this._grammarFactory = null; return; } finally { (self).define = globalDefine; } - - this._grammarFactory = new TMGrammarFactory({ - logTrace: (msg: string) => {/* console.log(msg) */ }, - logError: (msg: string, err: any) => console.error(msg, err), - readFile: (resource: URI) => this._host.readFile(resource) - }, grammarDefinitions, vscodeTextmate, undefined); } public acceptNewModel(data: IRawModelData): void { @@ -175,7 +175,10 @@ export class TextMateWorker { } } - public getOrCreateGrammar(languageId: LanguageId): Promise { + public getOrCreateGrammar(languageId: LanguageId): Promise { + if (!this._grammarFactory) { + return Promise.resolve(null); + } if (!this._grammarCache[languageId]) { this._grammarCache[languageId] = this._grammarFactory.createGrammar(languageId); } @@ -183,7 +186,9 @@ export class TextMateWorker { } public acceptTheme(theme: IRawTheme): void { - this._grammarFactory.setTheme(theme); + if (this._grammarFactory) { + this._grammarFactory.setTheme(theme); + } } public _setTokens(resource: URI, versionId: number, tokens: Uint8Array): void { diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 7e7d7a77ab..9c9ba96540 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -46,6 +46,7 @@ export class BrowserTextFileService extends TextFileService { } if (!hasBackup) { + console.warn('Unload prevented: pending backups'); return true; // dirty without backup: veto } } @@ -54,4 +55,4 @@ export class BrowserTextFileService extends TextFileService { } } -registerSingleton(ITextFileService, BrowserTextFileService); \ No newline at end of file +registerSingleton(ITextFileService, BrowserTextFileService); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 520b3e7f99..04ce61edf4 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -308,7 +308,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil try { return await this.loadFromBackup(backup, options); } catch (error) { - // ignore error and continue to load as file below + this.logService.error(error); // ignore error and continue to load as file below } } } @@ -470,7 +470,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // We also want to trigger auto save if it is enabled to simulate the exact same behaviour // you would get if manually making the model dirty (fixes https://github.com/Microsoft/vscode/issues/16977) if (fromBackup) { - this.makeDirty(); + this.doMakeDirty(); if (this.autoSaveAfterMilliesEnabled) { this.doAutoSave(this.versionId); } @@ -549,7 +549,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.logService.trace('onModelContentChanged() - model content changed and marked as dirty', this.resource); // Mark as dirty - this.makeDirty(); + this.doMakeDirty(); // Start auto save process unless we are in conflict resolution mode and unless it is disabled if (this.autoSaveAfterMilliesEnabled) { @@ -564,7 +564,15 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.contentChangeEventScheduler.schedule(); } - private makeDirty(): void { + makeDirty(): void { + if (!this.isResolved()) { + return; // only resolved models can be marked dirty + } + + this.doMakeDirty(); + } + + private doMakeDirty(): void { // Track dirty state and version id const wasDirty = this.dirty; @@ -915,7 +923,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil TextFileEditorModel.saveErrorHandler.onSaveError(error, this); } - isDirty(): boolean { + isDirty(): boolean { // {{SQL CARBON EDIT}} strict-null-check return this.dirty; } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 38e42ee73c..70cc9f3276 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -38,46 +38,16 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE private readonly _onModelOrphanedChanged: Emitter = this._register(new Emitter()); readonly onModelOrphanedChanged: Event = this._onModelOrphanedChanged.event; - private _onModelsDirtyEvent: Event; - private _onModelsSaveError: Event; - private _onModelsSaved: Event; - private _onModelsReverted: Event; - - private mapResourceToDisposeListener: ResourceMap; - private mapResourceToStateChangeListener: ResourceMap; - private mapResourceToModelContentChangeListener: ResourceMap; - private mapResourceToModel: ResourceMap; - private mapResourceToPendingModelLoaders: ResourceMap>; - - constructor( - @ILifecycleService private readonly lifecycleService: ILifecycleService, - @IInstantiationService private readonly instantiationService: IInstantiationService - ) { - super(); - - this.mapResourceToModel = new ResourceMap(); - this.mapResourceToDisposeListener = new ResourceMap(); - this.mapResourceToStateChangeListener = new ResourceMap(); - this.mapResourceToModelContentChangeListener = new ResourceMap(); - this.mapResourceToPendingModelLoaders = new ResourceMap>(); - - this.registerListeners(); - } - - private registerListeners(): void { - - // Lifecycle - this.lifecycleService.onShutdown(this.dispose, this); - } - + private _onModelsDirty!: Event; get onModelsDirty(): Event { - if (!this._onModelsDirtyEvent) { - this._onModelsDirtyEvent = this.debounce(this.onModelDirty); + if (!this._onModelsDirty) { + this._onModelsDirty = this.debounce(this.onModelDirty); } - return this._onModelsDirtyEvent; + return this._onModelsDirty; } + private _onModelsSaveError!: Event; get onModelsSaveError(): Event { if (!this._onModelsSaveError) { this._onModelsSaveError = this.debounce(this.onModelSaveError); @@ -86,6 +56,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return this._onModelsSaveError; } + private _onModelsSaved!: Event; get onModelsSaved(): Event { if (!this._onModelsSaved) { this._onModelsSaved = this.debounce(this.onModelSaved); @@ -94,6 +65,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return this._onModelsSaved; } + private _onModelsReverted!: Event; get onModelsReverted(): Event { if (!this._onModelsReverted) { this._onModelsReverted = this.debounce(this.onModelReverted); @@ -102,6 +74,27 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return this._onModelsReverted; } + private mapResourceToDisposeListener = new ResourceMap(); + private mapResourceToStateChangeListener = new ResourceMap(); + private mapResourceToModelContentChangeListener = new ResourceMap(); + private mapResourceToModel = new ResourceMap(); + private mapResourceToPendingModelLoaders = new ResourceMap>(); + + constructor( + @ILifecycleService private readonly lifecycleService: ILifecycleService, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super(); + + this.registerListeners(); + } + + private registerListeners(): void { + + // Lifecycle + this.lifecycleService.onShutdown(this.dispose, this); + } + private debounce(event: Event): Event { return Event.debounce(event, (prev: TextFileModelChangeEvent[], cur: TextFileModelChangeEvent) => { if (!prev) { @@ -327,4 +320,4 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE this.clear(); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts index a722a4e6dc..bd85cbd100 100644 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ b/src/vs/workbench/services/textfile/common/textFileService.ts @@ -46,7 +46,7 @@ import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; */ export abstract class TextFileService extends Disposable implements ITextFileService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private readonly _onAutoSaveConfigurationChange: Emitter = this._register(new Emitter()); readonly onAutoSaveConfigurationChange: Event = this._onAutoSaveConfigurationChange.event; @@ -64,9 +64,9 @@ export abstract class TextFileService extends Disposable implements ITextFileSer private currentFilesAssociationConfig: { [key: string]: string; }; private configuredAutoSaveDelay?: number; - private configuredAutoSaveOnFocusChange: boolean; - private configuredAutoSaveOnWindowChange: boolean; - private configuredHotExit: string; + private configuredAutoSaveOnFocusChange: boolean | undefined; + private configuredAutoSaveOnWindowChange: boolean | undefined; + private configuredHotExit: string | undefined; private autoSaveContext: IContextKey; constructor( @@ -443,9 +443,94 @@ export abstract class TextFileService extends Disposable implements ITextFileSer } async move(source: URI, target: URI, overwrite?: boolean): Promise { + + // await onWillMove event joiners + await this.notifyOnWillMove(source, target); + + // find all models that related to either source or target (can be many if resource is a folder) + const sourceModels: ITextFileEditorModel[] = []; + const conflictingModels: ITextFileEditorModel[] = []; + for (const model of this.getFileModels()) { + const resource = model.getResource(); + + if (isEqualOrParent(resource, target, false /* do not ignorecase, see https://github.com/Microsoft/vscode/issues/56384 */)) { + conflictingModels.push(model); + } + + if (isEqualOrParent(resource, source)) { + sourceModels.push(model); + } + } + + // remember each source model to load again after move is done + // with optional content to restore if it was dirty + type ModelToRestore = { resource: URI; snapshot?: ITextSnapshot }; + const modelsToRestore: ModelToRestore[] = []; + for (const sourceModel of sourceModels) { + const sourceModelResource = sourceModel.getResource(); + + // If the source is the actual model, just use target as new resource + let modelToRestoreResource: URI; + if (isEqual(sourceModelResource, source)) { + modelToRestoreResource = target; + } + + // Otherwise a parent folder of the source is being moved, so we need + // to compute the target resource based on that + else { + modelToRestoreResource = joinPath(target, sourceModelResource.path.substr(source.path.length + 1)); + } + + const modelToRestore: ModelToRestore = { resource: modelToRestoreResource }; + if (sourceModel.isDirty()) { + modelToRestore.snapshot = sourceModel.createSnapshot(); + } + + modelsToRestore.push(modelToRestore); + } + + // in order to move, we need to soft revert all dirty models, + // both from the source as well as the target if any + const dirtyModels = [...sourceModels, ...conflictingModels].filter(model => model.isDirty()); + await this.revertAll(dirtyModels.map(dirtyModel => dirtyModel.getResource()), { soft: true }); + + // now we can rename the source to target via file operation + let stat: IFileStatWithMetadata; + try { + stat = await this.fileService.move(source, target, overwrite); + } catch (error) { + + // in case of any error, ensure to set dirty flag back + dirtyModels.forEach(dirtyModel => dirtyModel.makeDirty()); + + throw error; + } + + // finally, restore models that we had loaded previously + await Promise.all(modelsToRestore.map(async modelToRestore => { + + // restore the model, forcing a reload. this is important because + // we know the file has changed on disk after the move and the + // model might have still existed with the previous state. this + // ensures we are not tracking a stale state. + const restoredModel = await this.models.loadOrCreate(modelToRestore.resource, { reload: { async: false } }); + + // restore previous dirty content if any and ensure to mark + // the model as dirty + if (modelToRestore.snapshot && restoredModel.isResolved()) { + this.modelService.updateModel(restoredModel.textEditorModel, createTextBufferFactoryFromSnapshot(modelToRestore.snapshot)); + + restoredModel.makeDirty(); + } + })); + + return stat; + } + + private async notifyOnWillMove(source: URI, target: URI): Promise { const waitForPromises: Promise[] = []; - // Event + // fire event this._onWillMove.fire({ oldResource: source, newResource: target, @@ -458,58 +543,6 @@ export abstract class TextFileService extends Disposable implements ITextFileSer Object.freeze(waitForPromises); await Promise.all(waitForPromises); - - // Handle target models if existing (if target URI is a folder, this can be multiple) - const dirtyTargetModels = this.getDirtyFileModels().filter(model => isEqualOrParent(model.getResource(), target, false /* do not ignorecase, see https://github.com/Microsoft/vscode/issues/56384 */)); - if (dirtyTargetModels.length) { - await this.revertAll(dirtyTargetModels.map(targetModel => targetModel.getResource()), { soft: true }); - } - - // Handle dirty source models if existing (if source URI is a folder, this can be multiple) - const dirtySourceModels = this.getDirtyFileModels().filter(model => isEqualOrParent(model.getResource(), source)); - const dirtyTargetModelUris: URI[] = []; - if (dirtySourceModels.length) { - await Promise.all(dirtySourceModels.map(async sourceModel => { - const sourceModelResource = sourceModel.getResource(); - let targetModelResource: URI; - - // If the source is the actual model, just use target as new resource - if (isEqual(sourceModelResource, source)) { - targetModelResource = target; - } - - // Otherwise a parent folder of the source is being moved, so we need - // to compute the target resource based on that - else { - targetModelResource = sourceModelResource.with({ path: joinPath(target, sourceModelResource.path.substr(source.path.length + 1)).path }); - } - - // Remember as dirty target model to load after the operation - dirtyTargetModelUris.push(targetModelResource); - - // Backup dirty source model to the target resource it will become later - await sourceModel.backup(targetModelResource); - })); - } - - // Soft revert the dirty source files if any - await this.revertAll(dirtySourceModels.map(dirtySourceModel => dirtySourceModel.getResource()), { soft: true }); - - // Rename to target - try { - const stat = await this.fileService.move(source, target, overwrite); - - // Load models that were dirty before - await Promise.all(dirtyTargetModelUris.map(dirtyTargetModel => this.models.loadOrCreate(dirtyTargetModel))); - - return stat; - } catch (error) { - - // In case of an error, discard any dirty target backups that were made - await Promise.all(dirtyTargetModelUris.map(dirtyTargetModel => this.backupFileService.discardResourceBackup(dirtyTargetModel))); - - throw error; - } } //#endregion @@ -537,7 +570,9 @@ export abstract class TextFileService extends Disposable implements ITextFileSer async confirmSave(resources?: URI[]): Promise { if (this.environmentService.isExtensionDevelopment) { - return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) + if (!this.environmentService.args['extension-development-confirm-save']) { + return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) + } } const resourcesToConfirm = this.getDirty(resources); @@ -856,7 +891,7 @@ export abstract class TextFileService extends Disposable implements ITextFileSer return false; } - // take over encoding, mode (only if more specific) and model value from source model + // take over model value, encoding and mode (only if more specific) from source model targetModel.updatePreferredEncoding(sourceModel.getEncoding()); if (sourceModel.isResolved() && targetModel.isResolved()) { this.modelService.updateModel(targetModel.textEditorModel, createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot())); @@ -1010,8 +1045,8 @@ export abstract class TextFileService extends Disposable implements ITextFileSer getAutoSaveConfiguration(): IAutoSaveConfiguration { return { autoSaveDelay: this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0 ? this.configuredAutoSaveDelay : undefined, - autoSaveFocusChange: this.configuredAutoSaveOnFocusChange, - autoSaveApplicationChange: this.configuredAutoSaveOnWindowChange + autoSaveFocusChange: !!this.configuredAutoSaveOnFocusChange, + autoSaveApplicationChange: !!this.configuredAutoSaveOnWindowChange }; } diff --git a/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts b/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts index 1780a2570c..ead7803006 100644 --- a/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts +++ b/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts @@ -17,7 +17,7 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export class TextResourcePropertiesService implements ITextResourcePropertiesService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private remoteEnvironment: IRemoteAgentEnvironment | null = null; @@ -55,4 +55,4 @@ export class TextResourcePropertiesService implements ITextResourcePropertiesSer } } -registerSingleton(ITextResourcePropertiesService, TextResourcePropertiesService, true); \ No newline at end of file +registerSingleton(ITextResourcePropertiesService, TextResourcePropertiesService, true); diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 4dc3f44ff5..f5a61d77de 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -470,7 +470,9 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport hasBackup(): boolean; - isDirty(): boolean; + isDirty(): boolean; // {{SQL CARBON EDIT}} strict-null-check + + makeDirty(): void; isResolved(): this is IResolvedTextFileEditorModel; @@ -522,7 +524,7 @@ export function stringToSnapshot(value: string): ITextSnapshot { } export class TextSnapshotReadable implements VSBufferReadable { - private preambleHandled: boolean; + private preambleHandled = false; constructor(private snapshot: ITextSnapshot, private preamble?: string) { } @@ -805,4 +807,4 @@ export const SUPPORTED_ENCODINGS: { [encoding: string]: { labelLong: string; lab labelShort: 'CP 850', order: 47 } -}; \ No newline at end of file +}; diff --git a/src/vs/workbench/services/textfile/node/textFileService.ts b/src/vs/workbench/services/textfile/node/textFileService.ts index c0e4ad7fde..0046c4ff53 100644 --- a/src/vs/workbench/services/textfile/node/textFileService.ts +++ b/src/vs/workbench/services/textfile/node/textFileService.ts @@ -17,20 +17,20 @@ import { isMacintosh } from 'vs/base/common/platform'; import product from 'vs/platform/product/node/product'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { UTF8, UTF8_with_bom, UTF16be, UTF16le, encodingExists, encodeStream, UTF8_BOM, UTF16be_BOM, UTF16le_BOM, toDecodeStream, IDecodeStreamResult, detectEncodingByBOMFromBuffer } from 'vs/base/node/encoding'; +import { UTF8, UTF8_with_bom, UTF16be, UTF16le, encodingExists, encodeStream, UTF8_BOM, toDecodeStream, IDecodeStreamResult, detectEncodingByBOMFromBuffer, isUTFEncoding } from 'vs/base/node/encoding'; import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; import { joinPath, extname, isEqualOrParent } from 'vs/base/common/resources'; import { Disposable } from 'vs/base/common/lifecycle'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { VSBufferReadable, VSBuffer, VSBufferReadableStream } from 'vs/base/common/buffer'; +import { VSBufferReadable } from 'vs/base/common/buffer'; import { Readable } from 'stream'; -import { isUndefinedOrNull } from 'vs/base/common/types'; import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; import { ITextSnapshot } from 'vs/editor/common/model'; +import { nodeReadableToString, streamToNodeReadable, nodeStreamToVSBufferReadable } from 'vs/base/node/stream'; export class NodeTextFileService extends TextFileService { - private _encoding: EncodingOracle; + private _encoding!: EncodingOracle; get encoding(): EncodingOracle { if (!this._encoding) { this._encoding = this._register(this.instantiationService.createInstance(EncodingOracle)); @@ -45,7 +45,7 @@ export class NodeTextFileService extends TextFileService { return { ...bufferStream, encoding: decoder.detected.encoding || UTF8, - value: await this.nodeReadableToString(decoder.stream) + value: await nodeReadableToString(decoder.stream) }; } @@ -68,7 +68,7 @@ export class NodeTextFileService extends TextFileService { const bufferStream = await this.fileService.readFileStream(resource, options); // read through encoding library - const decoder = await toDecodeStream(this.streamToNodeReadable(bufferStream.value), { + const decoder = await toDecodeStream(streamToNodeReadable(bufferStream.value), { guessEncoding: (options && options.autoGuessEncoding) || this.textResourceConfigurationService.getValue(resource, 'files.autoGuessEncoding'), overwriteEncoding: detectedEncoding => this.encoding.getReadEncoding(resource, options, detectedEncoding) }); @@ -108,60 +108,6 @@ export class NodeTextFileService extends TextFileService { return ensuredOptions; } - private streamToNodeReadable(stream: VSBufferReadableStream): Readable { - return new class extends Readable { - private listening = false; - - _read(size?: number): void { - if (!this.listening) { - this.listening = true; - - // Data - stream.on('data', data => { - try { - if (!this.push(data.buffer)) { - stream.pause(); // pause the stream if we should not push anymore - } - } catch (error) { - this.emit(error); - } - }); - - // End - stream.on('end', () => { - try { - this.push(null); // signal EOS - } catch (error) { - this.emit(error); - } - }); - - // Error - stream.on('error', error => this.emit(error)); - } - - // ensure the stream is flowing - stream.resume(); - } - - _destroy(error: Error | null, callback: (error: Error | null) => void): void { - stream.destroy(); - - callback(null); - } - }; - } - - private nodeReadableToString(stream: NodeJS.ReadableStream): Promise { - return new Promise((resolve, reject) => { - let result = ''; - - stream.on('data', chunk => result += chunk); - stream.on('error', reject); - stream.on('end', () => resolve(result)); - }); - } - protected async doCreate(resource: URI, value?: string, options?: ICreateFileOptions): Promise { // check for encoding @@ -238,7 +184,7 @@ export class NodeTextFileService extends TextFileService { const encodedReadable = readable.pipe(encoder); - return this.nodeStreamToReadable(encodedReadable, encoding, addBOM); + return nodeStreamToVSBufferReadable(encodedReadable, addBOM && isUTFEncoding(encoding) ? { encoding } : undefined); } private snapshotToNodeReadable(snapshot: ITextSnapshot): Readable { @@ -266,53 +212,6 @@ export class NodeTextFileService extends TextFileService { }); } - private nodeStreamToReadable(stream: NodeJS.ReadWriteStream, encoding: string, addBOM: boolean): VSBufferReadable { - let bytesRead = 0; - let done = false; - - return { - read(): VSBuffer | null { - if (done) { - return null; - } - - const res = stream.read(); - if (isUndefinedOrNull(res)) { - done = true; - - // If we are instructed to add a BOM but we detect that no - // bytes have been read, we must ensure to return the BOM - // ourselves so that we comply with the contract. - if (bytesRead === 0 && addBOM) { - switch (encoding) { - case UTF8: - case UTF8_with_bom: - return VSBuffer.wrap(Buffer.from(UTF8_BOM)); - case UTF16be: - return VSBuffer.wrap(Buffer.from(UTF16be_BOM)); - case UTF16le: - return VSBuffer.wrap(Buffer.from(UTF16le_BOM)); - } - } - - return null; - } - - // Handle String - if (typeof res === 'string') { - bytesRead += res.length; - return VSBuffer.fromString(res); - } - - // Handle Buffer - else { - bytesRead += res.byteLength; - return VSBuffer.wrap(res); - } - } - }; - } - private async writeElevated(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise { // write into a tmp file first diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts index cb870fd3d3..66e12f72a7 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts @@ -219,6 +219,33 @@ suite('Files - TextFileEditorModel', () => { assert.ok(model.isDirty()); }); + test('Make Dirty', async function () { + let eventCounter = 0; + + const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + + model.makeDirty(); + assert.ok(!model.isDirty()); // needs to be resolved + + await model.load(); + model.textEditorModel!.setValue('foo'); + assert.ok(model.isDirty()); + + await model.revert(true /* soft revert */); + assert.ok(!model.isDirty()); + + model.onDidStateChange(e => { + if (e === StateChange.DIRTY) { + eventCounter++; + } + }); + + model.makeDirty(); + assert.ok(model.isDirty()); + assert.equal(eventCounter, 1); + model.dispose(); + }); + test('File not modified error is handled gracefully', async function () { let model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); diff --git a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts index 118d976913..d597e4538c 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts @@ -49,7 +49,7 @@ class ServiceAccessor { class TestNodeTextFileService extends NodeTextFileService { - private _testEncoding: TestEncodingOracle; + private _testEncoding: TestEncodingOracle | undefined; get encoding(): TestEncodingOracle { if (!this._testEncoding) { this._testEncoding = this._register(this.instantiationService.createInstance(TestEncodingOracle)); diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index 3ad5111692..7d422457a7 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -37,7 +37,7 @@ class ServiceAccessor { class BeforeShutdownEventImpl implements BeforeShutdownEvent { - public value: boolean | Promise; + public value: boolean | Promise | undefined; public reason = ShutdownReason.CLOSE; veto(value: boolean | Promise): void { @@ -272,8 +272,16 @@ suite('Files - TextFileService', () => { }); test('move - dirty file', async function () { - let sourceModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - let targetModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_target.txt'), 'utf8', undefined); + await testMove(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt')); + }); + + test('move - dirty file (target exists and is dirty)', async function () { + await testMove(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt'), true); + }); + + async function testMove(source: URI, target: URI, targetDirty?: boolean): Promise { + let sourceModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, source, 'utf8', undefined); + let targetModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, target, 'utf8', undefined); (accessor.textFileService.models).add(sourceModel.getResource(), sourceModel); (accessor.textFileService.models).add(targetModel.getResource(), targetModel); @@ -283,11 +291,22 @@ suite('Files - TextFileService', () => { sourceModel.textEditorModel!.setValue('foo'); assert.ok(service.isDirty(sourceModel.getResource())); + if (targetDirty) { + await targetModel.load(); + targetModel.textEditorModel!.setValue('bar'); + assert.ok(service.isDirty(targetModel.getResource())); + } + await service.move(sourceModel.getResource(), targetModel.getResource(), true); + + assert.equal(targetModel.textEditorModel!.getValue(), 'foo'); + assert.ok(!service.isDirty(sourceModel.getResource())); + assert.ok(service.isDirty(targetModel.getResource())); + sourceModel.dispose(); targetModel.dispose(); - }); + } // {{SQL CARBON EDIT}} /* diff --git a/src/vs/workbench/services/timer/electron-browser/timerService.ts b/src/vs/workbench/services/timer/electron-browser/timerService.ts index 1f2dbf365d..28a64511de 100644 --- a/src/vs/workbench/services/timer/electron-browser/timerService.ts +++ b/src/vs/workbench/services/timer/electron-browser/timerService.ts @@ -180,7 +180,7 @@ export interface IStartupMetrics { /** * The time it took to create a new renderer (browser window) and to initialize that to the point - * of load the main-bundle (`workbench.main.js`). + * of load the main-bundle (`workbench.desktop.main.js`). * * * Happens in the main-process *and* the renderer-process * * Measured with the `main:loadWindow` and `willLoadWorkbenchMain` performance marks. @@ -208,7 +208,7 @@ export interface IStartupMetrics { readonly ellapsedWorkspaceServiceInit: number; /** - * The time it took to load the main-bundle of the workbench, e.g. `workbench.main.js`. + * The time it took to load the main-bundle of the workbench, e.g. `workbench.desktop.main.js`. * * * Happens in the renderer-process * * Measured with the `willLoadWorkbenchMain` and `didLoadWorkbenchMain` performance marks. @@ -305,7 +305,7 @@ class TimerService implements ITimerService { _serviceBrand: any; - private _startupMetrics: Promise; + private _startupMetrics?: Promise; constructor( @IWindowsService private readonly _windowsService: IWindowsService, diff --git a/src/vs/workbench/services/untitled/common/untitledEditorService.ts b/src/vs/workbench/services/untitled/common/untitledEditorService.ts index cbfe97396e..f7cfcc9f10 100644 --- a/src/vs/workbench/services/untitled/common/untitledEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledEditorService.ts @@ -118,7 +118,7 @@ export interface IUntitledEditorService { export class UntitledEditorService extends Disposable implements IUntitledEditorService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private mapResourceToInput = new ResourceMap(); private mapResourceToAssociatedFilePath = new ResourceMap(); @@ -291,4 +291,4 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor } } -registerSingleton(IUntitledEditorService, UntitledEditorService, true); \ No newline at end of file +registerSingleton(IUntitledEditorService, UntitledEditorService, true); diff --git a/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts b/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts index d6019f344e..679b874d12 100644 --- a/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts @@ -36,7 +36,7 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export class WorkspaceEditingService implements IWorkspaceEditingService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; constructor( @IJSONEditingService private readonly jsonEditingService: IJSONEditingService, @@ -220,6 +220,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { private async doAddFolders(foldersToAdd: IWorkspaceFolderCreationData[], index?: number, donotNotifyError: boolean = false): Promise { const state = this.contextService.getWorkbenchState(); if (this.environmentService.configuration.remoteAuthority) { + // Do not allow workspace folders with scheme different than the current remote scheme const schemas = this.contextService.getWorkspace().folders.map(f => f.uri.scheme); if (schemas.length && foldersToAdd.some(f => schemas.indexOf(f.uri.scheme) === -1)) { @@ -286,6 +287,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { if (path && !await this.isValidTargetWorkspacePath(path)) { return; } + const remoteAuthority = this.environmentService.configuration.remoteAuthority; const untitledWorkspace = await this.workspaceService.createUntitledWorkspace(folders, remoteAuthority); if (path) { @@ -293,6 +295,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { } else { path = untitledWorkspace.configPath; } + return this.enterWorkspace(path); } @@ -300,17 +303,18 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { if (!await this.isValidTargetWorkspacePath(path)) { return; } + const workspaceIdentifier = this.getCurrentWorkspaceIdentifier(); if (!workspaceIdentifier) { return; } + await this.saveWorkspaceAs(workspaceIdentifier, path); return this.enterWorkspace(path); } async isValidTargetWorkspacePath(path: URI): Promise { - const windows = await this.windowsService.getWindows(); // Prevent overwriting a workspace that is currently opened in another window @@ -382,30 +386,38 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { } const workspace = await this.workspaceService.getWorkspaceIdentifier(path); + // Settings migration (only if we come from a folder workspace) if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) { await this.migrateWorkspaceSettings(workspace); } + const workspaceImpl = this.contextService as WorkspaceService; await workspaceImpl.initialize(workspace); - // Restart extension host if first root folder changed (impact on deprecated workspace.rootPath API) - // Stop the extension host first to give extensions most time to shutdown - this.extensionService.stopExtensionHost(); - const result = await this.windowService.enterWorkspace(path); if (result) { + + // Migrate storage to new workspace await this.migrateStorage(result.workspace); + // Reinitialize backup service + this.environmentService.configuration.backupPath = result.backupPath; + this.environmentService.configuration.backupWorkspaceResource = result.backupPath ? toBackupWorkspaceResource(result.backupPath, this.environmentService) : undefined; if (this.backupFileService instanceof BackupFileService) { - this.backupFileService.initialize(toBackupWorkspaceResource(result.backupPath!, this.environmentService)); + this.backupFileService.reinitialize(); } } + // TODO@aeschli: workaround until restarting works if (this.environmentService.configuration.remoteAuthority) { - this.windowService.reloadWindow(); // TODO aeschli: workaround until restarting works - } else { - this.extensionService.startExtensionHost(); + this.windowService.reloadWindow(); + } + + // Restart the extension host: entering a workspace means a new location for + // storage and potentially a change in the workspace.rootPath property. + else { + this.extensionService.restartExtensionHost(); } } @@ -448,4 +460,4 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { } } -registerSingleton(IWorkspaceEditingService, WorkspaceEditingService, true); \ No newline at end of file +registerSingleton(IWorkspaceEditingService, WorkspaceEditingService, true); diff --git a/src/vs/workbench/test/browser/part.test.ts b/src/vs/workbench/test/browser/part.test.ts index c051ea2112..bdc864499b 100644 --- a/src/vs/workbench/test/browser/part.test.ts +++ b/src/vs/workbench/test/browser/part.test.ts @@ -13,10 +13,10 @@ import { StorageScope } from 'vs/platform/storage/common/storage'; class SimplePart extends Part { - minimumWidth: number; - maximumWidth: number; - minimumHeight: number; - maximumHeight: number; + minimumWidth: number = 50; + maximumWidth: number = 50; + minimumHeight: number = 50; + maximumHeight: number = 50; layout(width: number, height: number): void { throw new Error('Method not implemented.'); diff --git a/src/vs/workbench/test/common/notifications.test.ts b/src/vs/workbench/test/common/notifications.test.ts index 01b138d94e..46ae2ce2b7 100644 --- a/src/vs/workbench/test/common/notifications.test.ts +++ b/src/vs/workbench/test/common/notifications.test.ts @@ -103,7 +103,7 @@ suite('Notifications', () => { // Error with Action let item6 = NotificationViewItem.create({ severity: Severity.Error, message: createErrorWithActions('Hello Error', { actions: [new Action('id', 'label')] }) })!; - assert.equal(item6.actions.primary!.length, 1); + assert.equal(item6.actions!.primary!.length, 1); // Links let item7 = NotificationViewItem.create({ severity: Severity.Info, message: 'Unable to [Link 1](http://link1.com) open [Link 2](command:open.me "Open This") and [Link 3](command:without.title) and [Invalid Link4](ftp://link4.com)' })!; @@ -219,4 +219,4 @@ suite('Notifications', () => { disposable3.dispose(); assert.ok(!model.statusMessage); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts index 9e3a61156f..a76783fc0d 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts @@ -181,7 +181,7 @@ suite('ExtHostLanguageFeatureCommands', function () { test('executeWorkspaceSymbolProvider should accept empty string, #39522', async function () { disposables.push(extHost.registerWorkspaceSymbolProvider(nullExtensionDescription, { - provideWorkspaceSymbols(query): vscode.SymbolInformation[] { + provideWorkspaceSymbols(): vscode.SymbolInformation[] { return [new types.SymbolInformation('hello', types.SymbolKind.Array, new types.Range(0, 0, 0, 0), URI.parse('foo:bar')) as vscode.SymbolInformation]; } })); @@ -312,7 +312,7 @@ suite('ExtHostLanguageFeatureCommands', function () { test('reference search, back and forth', function () { disposables.push(extHost.registerReferenceProvider(nullExtensionDescription, defaultSelector, { - provideReferences(doc: any) { + provideReferences() { return [ new types.Location(URI.parse('some:uri/path'), new types.Range(0, 1, 0, 5)) ]; @@ -388,7 +388,7 @@ suite('ExtHostLanguageFeatureCommands', function () { test('Suggest, back and forth', function () { disposables.push(extHost.registerCompletionItemProvider(nullExtensionDescription, defaultSelector, { - provideCompletionItems(doc, pos): any { + provideCompletionItems(): any { let a = new types.CompletionItem('item1'); let b = new types.CompletionItem('item2'); b.textEdit = types.TextEdit.replace(new types.Range(0, 4, 0, 8), 'foo'); // overwite after @@ -631,6 +631,61 @@ suite('ExtHostLanguageFeatureCommands', function () { }); }); + test('vscode.executeCodeActionProvider passes Range to provider although Selection is passed in #77997', function () { + disposables.push(extHost.registerCodeActionProvider(nullExtensionDescription, defaultSelector, { + provideCodeActions(document, rangeOrSelection): vscode.CodeAction[] { + return [{ + command: { + arguments: [document, rangeOrSelection], + command: 'command', + title: 'command_title', + }, + kind: types.CodeActionKind.Empty.append('foo'), + title: 'title', + }]; + } + })); + + const selection = new types.Selection(0, 0, 1, 1); + + return rpcProtocol.sync().then(() => { + return commands.executeCommand('vscode.executeCodeActionProvider', model.uri, selection).then(value => { + assert.equal(value.length, 1); + const [first] = value; + assert.ok(first.command); + assert.ok(first.command!.arguments![1] instanceof types.Selection); + assert.ok(first.command!.arguments![1].isEqual(selection)); + }); + }); + }); + + test('vscode.executeCodeActionProvider results seem to be missing their `isPreferred` property #78098', function () { + disposables.push(extHost.registerCodeActionProvider(nullExtensionDescription, defaultSelector, { + provideCodeActions(document, rangeOrSelection): vscode.CodeAction[] { + return [{ + command: { + arguments: [document, rangeOrSelection], + command: 'command', + title: 'command_title', + }, + kind: types.CodeActionKind.Empty.append('foo'), + title: 'title', + isPreferred: true + }]; + } + })); + + const selection = new types.Selection(0, 0, 1, 1); + + return rpcProtocol.sync().then(() => { + return commands.executeCommand('vscode.executeCodeActionProvider', model.uri, selection).then(value => { + assert.equal(value.length, 1); + const [first] = value; + assert.equal(first.isPreferred, true); + }); + }); + }); + // --- code lens test('CodeLens, back and forth', function () { @@ -724,7 +779,7 @@ suite('ExtHostLanguageFeatureCommands', function () { provideDocumentColors(): vscode.ColorInformation[] { return [new types.ColorInformation(new types.Range(0, 0, 0, 20), new types.Color(0.1, 0.2, 0.3, 0.4))]; }, - provideColorPresentations(color: vscode.Color, context: { range: vscode.Range, document: vscode.TextDocument }): vscode.ColorPresentation[] { + provideColorPresentations(): vscode.ColorPresentation[] { const cp = new types.ColorPresentation('#ABC'); cp.textEdit = types.TextEdit.replace(new types.Range(1, 0, 1, 20), '#ABC'); cp.additionalTextEdits = [types.TextEdit.insert(new types.Position(2, 20), '*')]; diff --git a/src/vs/workbench/test/electron-browser/api/extHostCommands.test.ts b/src/vs/workbench/test/electron-browser/api/extHostCommands.test.ts index 9bc2b444dc..f8536d932d 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostCommands.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostCommands.test.ts @@ -26,7 +26,10 @@ suite('ExtHostCommands', function () { } }; - const commands = new ExtHostCommands(SingleProxyRPCProtocol(shape), new NullLogService()); + const commands = new ExtHostCommands( + SingleProxyRPCProtocol(shape), + new NullLogService() + ); commands.registerCommand(true, 'foo', (): any => { }).dispose(); assert.equal(lastUnregister!, 'foo'); assert.equal(CommandsRegistry.getCommand('foo'), undefined); @@ -46,7 +49,10 @@ suite('ExtHostCommands', function () { } }; - const commands = new ExtHostCommands(SingleProxyRPCProtocol(shape), new NullLogService()); + const commands = new ExtHostCommands( + SingleProxyRPCProtocol(shape), + new NullLogService() + ); const reg = commands.registerCommand(true, 'foo', (): any => { }); reg.dispose(); reg.dispose(); diff --git a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts index 389601f20e..f7bcb8b76d 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts @@ -15,7 +15,7 @@ import { IWorkspaceFolder, WorkspaceFolder } from 'vs/platform/workspace/common/ import { ConfigurationTarget, IConfigurationModel } from 'vs/platform/configuration/common/configuration'; import { NullLogService } from 'vs/platform/log/common/log'; import { assign } from 'vs/base/common/objects'; -import { Counter } from 'vs/base/common/numbers'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; suite('ExtHostConfiguration', function () { @@ -27,11 +27,15 @@ suite('ExtHostConfiguration', function () { } } + function createExtHostWorkspace(): ExtHostWorkspace { + return new ExtHostWorkspace(new TestRPCProtocol(), new class extends mock() { }, new NullLogService()); + } + function createExtHostConfiguration(contents: any = Object.create(null), shape?: MainThreadConfigurationShape) { if (!shape) { shape = new class extends mock() { }; } - return new ExtHostConfigProvider(shape, new ExtHostWorkspace(new TestRPCProtocol(), new NullLogService(), new Counter()), createConfigurationData(contents)); + return new ExtHostConfigProvider(shape, createExtHostWorkspace(), createConfigurationData(contents)); } function createConfigurationData(contents: any): IConfigurationInitData { @@ -264,7 +268,7 @@ suite('ExtHostConfiguration', function () { test('inspect in no workspace context', function () { const testObject = new ExtHostConfigProvider( new class extends mock() { }, - new ExtHostWorkspace(new TestRPCProtocol(), new NullLogService(), new Counter()), + createExtHostWorkspace(), { defaults: new ConfigurationModel({ 'editor': { @@ -304,7 +308,7 @@ suite('ExtHostConfiguration', function () { } }, ['editor.wordWrap']); folders.push([workspaceUri, workspace]); - const extHostWorkspace = new ExtHostWorkspace(new TestRPCProtocol(), new NullLogService(), new Counter()); + const extHostWorkspace = createExtHostWorkspace(); extHostWorkspace.$initializeWorkspace({ 'id': 'foo', 'folders': [aWorkspaceFolder(URI.file('foo'), 0)], @@ -379,7 +383,7 @@ suite('ExtHostConfiguration', function () { }, ['editor.wordWrap'])]); folders.push([thirdRoot, new ConfigurationModel({}, [])]); - const extHostWorkspace = new ExtHostWorkspace(new TestRPCProtocol(), new NullLogService(), new Counter()); + const extHostWorkspace = createExtHostWorkspace(); extHostWorkspace.$initializeWorkspace({ 'id': 'foo', 'folders': [aWorkspaceFolder(firstRoot, 0), aWorkspaceFolder(secondRoot, 1)], @@ -589,7 +593,7 @@ suite('ExtHostConfiguration', function () { test('configuration change event', (done) => { const workspaceFolder = aWorkspaceFolder(URI.file('folder1'), 0); - const extHostWorkspace = new ExtHostWorkspace(new TestRPCProtocol(), new NullLogService(), new Counter()); + const extHostWorkspace = createExtHostWorkspace(); extHostWorkspace.$initializeWorkspace({ 'id': 'foo', 'folders': [workspaceFolder], diff --git a/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts b/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts index 4ccc72d92f..37dfe46194 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts @@ -7,7 +7,7 @@ import { URI } from 'vs/base/common/uri'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { TextDocumentSaveReason, TextEdit, Position, EndOfLine } from 'vs/workbench/api/common/extHostTypes'; -import { MainThreadTextEditorsShape, WorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; +import { MainThreadTextEditorsShape, IWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/common/extHostDocumentSaveParticipant'; import { SingleProxyRPCProtocol } from './testRPCProtocol'; import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; @@ -262,9 +262,9 @@ suite('ExtHostDocumentSaveParticipant', () => { test('event delivery, pushEdits sync', () => { - let dto: WorkspaceEditDto; + let dto: IWorkspaceEditDto; const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, new class extends mock() { - $tryApplyWorkspaceEdit(_edits: WorkspaceEditDto) { + $tryApplyWorkspaceEdit(_edits: IWorkspaceEditDto) { dto = _edits; return Promise.resolve(true); } @@ -286,9 +286,9 @@ suite('ExtHostDocumentSaveParticipant', () => { test('event delivery, concurrent change', () => { - let edits: WorkspaceEditDto; + let edits: IWorkspaceEditDto; const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, new class extends mock() { - $tryApplyWorkspaceEdit(_edits: WorkspaceEditDto) { + $tryApplyWorkspaceEdit(_edits: IWorkspaceEditDto) { edits = _edits; return Promise.resolve(true); } @@ -323,7 +323,7 @@ suite('ExtHostDocumentSaveParticipant', () => { test('event delivery, two listeners -> two document states', () => { const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, new class extends mock() { - $tryApplyWorkspaceEdit(dto: WorkspaceEditDto) { + $tryApplyWorkspaceEdit(dto: IWorkspaceEditDto) { for (const edit of dto.edits) { if (!isResourceTextEdit(edit)) { diff --git a/src/vs/workbench/test/electron-browser/api/extHostDocumentsAndEditors.test.ts b/src/vs/workbench/test/electron-browser/api/extHostDocumentsAndEditors.test.ts index dc3556cfb4..8c682c971d 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDocumentsAndEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDocumentsAndEditors.test.ts @@ -6,17 +6,14 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { TestRPCProtocol } from 'vs/workbench/test/electron-browser/api/testRPCProtocol'; suite('ExtHostDocumentsAndEditors', () => { let editors: ExtHostDocumentsAndEditors; setup(function () { - editors = new ExtHostDocumentsAndEditors({ - getProxy: () => { return undefined!; }, - set: undefined!, - assertRegistered: undefined! - }); + editors = new ExtHostDocumentsAndEditors(new TestRPCProtocol()); }); test('The value of TextDocument.isClosed is incorrect when a text document is closed, #27949', () => { diff --git a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts index 7d72cc145a..3b8e5095c6 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts @@ -18,6 +18,9 @@ import { IFileMatch, IFileQuery, IPatternInfo, IRawFileMatch2, ISearchCompleteSt import { TestRPCProtocol } from 'vs/workbench/test/electron-browser/api/testRPCProtocol'; import * as vscode from 'vscode'; import { NullLogService } from 'vs/platform/log/common/log'; +import { URITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; +import { mock } from 'vs/workbench/test/electron-browser/api/mock'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; let rpcProtocol: TestRPCProtocol; let extHostSearch: ExtHostSearch; @@ -135,7 +138,17 @@ suite('ExtHostSearch', () => { rpcProtocol.set(MainContext.MainThreadSearch, mockMainThreadSearch); mockPFS = {}; - extHostSearch = new ExtHostSearch(rpcProtocol, null!, logService, mockPFS as any); + extHostSearch = new class extends ExtHostSearch { + constructor() { + super( + rpcProtocol, + new class extends mock() { remote = { isRemote: false, authority: undefined }; }, + new URITransformerService(null), + logService + ); + this._pfs = mockPFS as any; + } + }; }); teardown(() => { diff --git a/src/vs/workbench/test/electron-browser/api/extHostTextEditors.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTextEditors.test.ts index a7603f9c63..1f3e0ae797 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTextEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTextEditors.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; -import { MainContext, MainThreadTextEditorsShape, WorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; +import { MainContext, MainThreadTextEditorsShape, IWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { URI } from 'vs/base/common/uri'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; @@ -16,14 +16,14 @@ suite('ExtHostTextEditors.applyWorkspaceEdit', () => { const resource = URI.parse('foo:bar'); let editors: ExtHostEditors; - let workspaceResourceEdits: WorkspaceEditDto; + let workspaceResourceEdits: IWorkspaceEditDto; setup(() => { workspaceResourceEdits = null!; let rpcProtocol = new TestRPCProtocol(); rpcProtocol.set(MainContext.MainThreadTextEditors, new class extends mock() { - $tryApplyWorkspaceEdit(_workspaceResourceEdits: WorkspaceEditDto): Promise { + $tryApplyWorkspaceEdit(_workspaceResourceEdits: IWorkspaceEditDto): Promise { workspaceResourceEdits = _workspaceResourceEdits; return Promise.resolve(true); } diff --git a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts index ea19411591..a1355584e3 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts @@ -71,7 +71,10 @@ suite('ExtHostTreeView', function () { rpcProtocol.set(MainContext.MainThreadCommands, inst.createInstance(MainThreadCommands, rpcProtocol)); target = new RecordingShape(); - testObject = new ExtHostTreeViews(target, new ExtHostCommands(rpcProtocol, new NullLogService()), new NullLogService()); + testObject = new ExtHostTreeViews(target, new ExtHostCommands( + rpcProtocol, + new NullLogService() + ), new NullLogService()); onDidChangeTreeNode = new Emitter<{ key: string }>(); onDidChangeTreeNodeWithId = new Emitter<{ key: string }>(); testObject.createTreeView('testNodeTreeProvider', { treeDataProvider: aNodeTreeDataProvider() }, { enableProposedApi: true } as IExtensionDescription); diff --git a/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts b/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts index b022fcc154..d98bba7384 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts @@ -5,7 +5,6 @@ import * as assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { Counter } from 'vs/base/common/numbers'; import { basename } from 'vs/base/common/path'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -17,9 +16,15 @@ import { RelativePattern } from 'vs/workbench/api/common/extHostTypes'; import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { TestRPCProtocol } from './testRPCProtocol'; +import { ExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; -function createExtHostWorkspace(mainContext: IMainContext, data: IWorkspaceData, logService: ILogService, requestIdProvider: Counter): ExtHostWorkspace { - const result = new ExtHostWorkspace(mainContext, logService, requestIdProvider); +function createExtHostWorkspace(mainContext: IMainContext, data: IWorkspaceData, logService: ILogService): ExtHostWorkspace { + const result = new ExtHostWorkspace( + new ExtHostRpcService(mainContext), + new class extends mock() { workspace = data; }, + logService + ); result.$initializeWorkspace(data); return result; } @@ -45,7 +50,7 @@ suite('ExtHostWorkspace', function () { test('asRelativePath', () => { - const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/Applications/NewsWoWBot'), 0)], name: 'Test' }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/Applications/NewsWoWBot'), 0)], name: 'Test' }, new NullLogService()); assertAsRelativePath(ws, '/Coding/Applications/NewsWoWBot/bernd/das/brot', 'bernd/das/brot'); assertAsRelativePath(ws, '/Apps/DartPubCache/hosted/pub.dartlang.org/convert-2.0.1/lib/src/hex.dart', @@ -59,7 +64,7 @@ suite('ExtHostWorkspace', function () { test('asRelativePath, same paths, #11402', function () { const root = '/home/aeschli/workspaces/samples/docker'; const input = '/home/aeschli/workspaces/samples/docker'; - const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); assertAsRelativePath(ws, input, input); @@ -68,20 +73,20 @@ suite('ExtHostWorkspace', function () { }); test('asRelativePath, no workspace', function () { - const ws = createExtHostWorkspace(new TestRPCProtocol(), null!, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(new TestRPCProtocol(), null!, new NullLogService()); assertAsRelativePath(ws, '', ''); assertAsRelativePath(ws, '/foo/bar', '/foo/bar'); }); test('asRelativePath, multiple folders', function () { - const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }, new NullLogService()); assertAsRelativePath(ws, '/Coding/One/file.txt', 'One/file.txt'); assertAsRelativePath(ws, '/Coding/Two/files/out.txt', 'Two/files/out.txt'); assertAsRelativePath(ws, '/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt'); }); test('slightly inconsistent behaviour of asRelativePath and getWorkspaceFolder, #31553', function () { - const mrws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }, new NullLogService(), new Counter()); + const mrws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }, new NullLogService()); assertAsRelativePath(mrws, '/Coding/One/file.txt', 'One/file.txt'); assertAsRelativePath(mrws, '/Coding/One/file.txt', 'One/file.txt', true); @@ -93,7 +98,7 @@ suite('ExtHostWorkspace', function () { assertAsRelativePath(mrws, '/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt', true); assertAsRelativePath(mrws, '/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt', false); - const srws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0)], name: 'Test' }, new NullLogService(), new Counter()); + const srws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0)], name: 'Test' }, new NullLogService()); assertAsRelativePath(srws, '/Coding/One/file.txt', 'file.txt'); assertAsRelativePath(srws, '/Coding/One/file.txt', 'file.txt', false); assertAsRelativePath(srws, '/Coding/One/file.txt', 'One/file.txt', true); @@ -103,24 +108,24 @@ suite('ExtHostWorkspace', function () { }); test('getPath, legacy', function () { - let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); + let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService()); assert.equal(ws.getPath(), undefined); - ws = createExtHostWorkspace(new TestRPCProtocol(), null!, new NullLogService(), new Counter()); + ws = createExtHostWorkspace(new TestRPCProtocol(), null!, new NullLogService()); assert.equal(ws.getPath(), undefined); - ws = createExtHostWorkspace(new TestRPCProtocol(), undefined!, new NullLogService(), new Counter()); + ws = createExtHostWorkspace(new TestRPCProtocol(), undefined!, new NullLogService()); assert.equal(ws.getPath(), undefined); - ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.file('Folder'), 0), aWorkspaceFolderData(URI.file('Another/Folder'), 1)] }, new NullLogService(), new Counter()); + ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.file('Folder'), 0), aWorkspaceFolderData(URI.file('Another/Folder'), 1)] }, new NullLogService()); assert.equal(ws.getPath()!.replace(/\\/g, '/'), '/Folder'); - ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.file('/Folder'), 0)] }, new NullLogService(), new Counter()); + ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.file('/Folder'), 0)] }, new NullLogService()); assert.equal(ws.getPath()!.replace(/\\/g, '/'), '/Folder'); }); test('WorkspaceFolder has name and index', function () { - const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }, new NullLogService()); const [one, two] = ws.getWorkspaceFolders()!; @@ -139,7 +144,7 @@ suite('ExtHostWorkspace', function () { aWorkspaceFolderData(URI.file('/Coding/Two'), 1), aWorkspaceFolderData(URI.file('/Coding/Two/Nested'), 2) ] - }, new NullLogService(), new Counter()); + }, new NullLogService()); let folder = ws.getWorkspaceFolder(URI.file('/foo/bar')); assert.equal(folder, undefined); @@ -179,7 +184,7 @@ suite('ExtHostWorkspace', function () { }); test('Multiroot change event should have a delta, #29641', function (done) { - let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); + let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService()); let finished = false; const finish = (error?: any) => { @@ -242,7 +247,7 @@ suite('ExtHostWorkspace', function () { }); test('Multiroot change keeps existing workspaces live', function () { - let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] }, new NullLogService(), new Counter()); + let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] }, new NullLogService()); let firstFolder = ws.getWorkspaceFolders()![0]; ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar2'), 0), aWorkspaceFolderData(URI.parse('foo:bar'), 1, 'renamed')] }); @@ -262,7 +267,7 @@ suite('ExtHostWorkspace', function () { }); test('updateWorkspaceFolders - invalid arguments', function () { - let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); + let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService()); assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, null!, null!)); assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, 0, 0)); @@ -271,7 +276,7 @@ suite('ExtHostWorkspace', function () { assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, -1, 0)); assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, -1, -1)); - ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] }, new NullLogService(), new Counter()); + ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] }, new NullLogService()); assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, 1, 1)); assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, 0, 2)); @@ -289,11 +294,11 @@ suite('ExtHostWorkspace', function () { const protocol: IMainContext = { getProxy: () => { return undefined!; }, - set: undefined!, - assertRegistered: undefined! + set: () => { return undefined!; }, + assertRegistered: () => { } }; - const ws = createExtHostWorkspace(protocol, { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(protocol, { id: 'foo', name: 'Test', folders: [] }, new NullLogService()); // // Add one folder @@ -526,7 +531,7 @@ suite('ExtHostWorkspace', function () { } }; - let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); + let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService()); let sub = ws.onDidChangeWorkspace(e => { try { assert.throws(() => { @@ -549,7 +554,7 @@ suite('ExtHostWorkspace', function () { id: 'foo', name: 'Test', folders: [ aWorkspaceFolderData(URI.file('c:/Users/marek/Desktop/vsc_test/'), 0) ] - }, new NullLogService(), new Counter()); + }, new NullLogService()); assert.ok(ws.getWorkspaceFolder(URI.file('c:/Users/marek/Desktop/vsc_test/a.txt'))); assert.ok(ws.getWorkspaceFolder(URI.file('C:/Users/marek/Desktop/vsc_test/b.txt'))); @@ -583,7 +588,7 @@ suite('ExtHostWorkspace', function () { } }); - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); return ws.findFiles('foo', undefined, 10, new ExtensionIdentifier('test')).then(() => { assert(mainThreadCalled, 'mainThreadCalled'); }); @@ -604,7 +609,7 @@ suite('ExtHostWorkspace', function () { } }); - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); return ws.findFiles(new RelativePattern('/other/folder', 'glob/**'), undefined, 10, new ExtensionIdentifier('test')).then(() => { assert(mainThreadCalled, 'mainThreadCalled'); }); @@ -625,7 +630,7 @@ suite('ExtHostWorkspace', function () { } }); - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); return ws.findFiles(new RelativePattern('/other/folder', 'glob/**'), null!, 10, new ExtensionIdentifier('test')).then(() => { assert(mainThreadCalled, 'mainThreadCalled'); }); @@ -643,7 +648,7 @@ suite('ExtHostWorkspace', function () { } }); - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); const token = CancellationToken.Cancelled; return ws.findFiles(new RelativePattern('/other/folder', 'glob/**'), null!, 10, new ExtensionIdentifier('test'), token).then(() => { @@ -664,7 +669,7 @@ suite('ExtHostWorkspace', function () { } }); - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); return ws.findFiles('', new RelativePattern(root, 'glob/**'), 10, new ExtensionIdentifier('test')).then(() => { assert(mainThreadCalled, 'mainThreadCalled'); }); diff --git a/src/vs/workbench/test/electron-browser/api/testRPCProtocol.ts b/src/vs/workbench/test/electron-browser/api/testRPCProtocol.ts index fe4523346e..3f876deaea 100644 --- a/src/vs/workbench/test/electron-browser/api/testRPCProtocol.ts +++ b/src/vs/workbench/test/electron-browser/api/testRPCProtocol.ts @@ -7,9 +7,11 @@ import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyId import { CharCode } from 'vs/base/common/charCode'; import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { isThenable } from 'vs/base/common/async'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; -export function SingleProxyRPCProtocol(thing: any): IExtHostContext { +export function SingleProxyRPCProtocol(thing: any): IExtHostContext & IExtHostRpcService { return { + _serviceBrand: undefined, remoteAuthority: null!, getProxy(): T { return thing; @@ -21,13 +23,14 @@ export function SingleProxyRPCProtocol(thing: any): IExtHostContext { }; } -export class TestRPCProtocol implements IExtHostContext { +export class TestRPCProtocol implements IExtHostContext, IExtHostRpcService { + public _serviceBrand = undefined; public remoteAuthority = null!; private _callCountValue: number = 0; private _idle?: Promise; - private _completeIdle: Function; + private _completeIdle?: Function; private readonly _locals: { [id: string]: any; }; private readonly _proxies: { [id: string]: any; }; diff --git a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts index 1b8031cccf..6c7d354742 100644 --- a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as minimist from 'minimist'; +import * as minimist from 'vscode-minimist'; import * as path from 'vs/base/common/path'; import { CancellationToken } from 'vs/base/common/cancellation'; import { URI } from 'vs/base/common/uri'; diff --git a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts index 9f9ac6e88c..17ccae608e 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -13,7 +13,7 @@ import { ISearchService } from 'vs/workbench/services/search/common/search'; import { ITelemetryService, ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import * as minimist from 'minimist'; +import * as minimist from 'vscode-minimist'; import * as path from 'vs/base/common/path'; import { LocalSearchService } from 'vs/workbench/services/search/node/searchService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 8b6898e751..01bd3b1585 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -665,6 +665,7 @@ export class TestEditorGroupsService implements IEditorGroupsService { onDidAddGroup: Event = Event.None; onDidRemoveGroup: Event = Event.None; onDidMoveGroup: Event = Event.None; + onDidGroupIndexChange: Event = Event.None; onDidLayout: Event = Event.None; orientation: any; @@ -761,6 +762,7 @@ export class TestEditorGroup implements IEditorGroupView { disposed: boolean; editors: ReadonlyArray = []; label: string; + index: number; whenRestored: Promise = Promise.resolve(undefined); element: HTMLElement; minimumWidth: number; @@ -768,6 +770,9 @@ export class TestEditorGroup implements IEditorGroupView { minimumHeight: number; maximumHeight: number; + isEmpty = true; + isMinimized = false; + onWillDispose: Event = Event.None; onDidGroupChange: Event = Event.None; onWillCloseEditor: Event = Event.None; @@ -837,9 +842,8 @@ export class TestEditorGroup implements IEditorGroupView { throw new Error('not implemented'); } - isEmpty(): boolean { return true; } setActive(_isActive: boolean): void { } - setLabel(_label: string): void { } + notifyIndexChanged(_index: number): void { } dispose(): void { } toJSON(): object { return Object.create(null); } layout(_width: number, _height: number): void { } diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts new file mode 100644 index 0000000000..5edebd3b79 --- /dev/null +++ b/src/vs/workbench/workbench.common.main.ts @@ -0,0 +1,235 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//#region --- editor/workbench core + +import 'vs/editor/editor.all'; + +import 'vs/workbench/api/browser/extensionHost.contribution'; +import 'sql/workbench/api/electron-browser/extensionHost.contribution'; // {{SQL CARBON EDIT}} @anthonydresser add our extension contributions +import 'vs/workbench/browser/workbench.contribution'; + +//#endregion + + +//#region --- workbench actions + +import 'vs/workbench/browser/actions/layoutActions'; +import 'vs/workbench/browser/actions/windowActions'; +import 'vs/workbench/browser/actions/developerActions'; +import 'vs/workbench/browser/actions/listCommands'; +import 'vs/workbench/browser/actions/navigationActions'; +import 'vs/workbench/browser/parts/quickopen/quickOpenActions'; +import 'vs/workbench/browser/parts/quickinput/quickInputActions'; + +//#endregion + + +//#region --- API Extension Points + +import 'vs/workbench/api/common/menusExtensionPoint'; +import 'vs/workbench/api/common/configurationExtensionPoint'; +import 'vs/workbench/api/browser/viewsExtensionPoint'; + +//#endregion + + +//#region --- workbench parts + +import 'vs/workbench/browser/parts/quickinput/quickInput'; +import 'vs/workbench/browser/parts/quickopen/quickOpenController'; +import 'vs/workbench/browser/parts/titlebar/titlebarPart'; +import 'vs/workbench/browser/parts/editor/editorPart'; +import 'vs/workbench/browser/parts/activitybar/activitybarPart'; +import 'vs/workbench/browser/parts/panel/panelPart'; +import 'vs/workbench/browser/parts/sidebar/sidebarPart'; +import 'vs/workbench/browser/parts/statusbar/statusbarPart'; +import 'vs/workbench/browser/parts/views/views'; + +//#endregion + + +//#region --- workbench services + +import 'vs/workbench/services/bulkEdit/browser/bulkEditService'; +import 'vs/workbench/services/keybinding/common/keybindingEditing'; +import 'vs/workbench/services/decorations/browser/decorationsService'; +import 'vs/workbench/services/progress/browser/progressService'; +import 'vs/workbench/services/editor/browser/codeEditorService'; +import 'vs/workbench/services/preferences/browser/preferencesService'; +import 'vs/workbench/services/configuration/common/jsonEditingService'; +import 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; +import 'vs/workbench/services/dialogs/browser/fileDialogService'; +import 'vs/workbench/services/editor/browser/editorService'; +import 'vs/workbench/services/history/browser/history'; +import 'vs/workbench/services/activity/browser/activityService'; +import 'vs/workbench/services/keybinding/browser/keybindingService'; +import 'vs/workbench/services/untitled/common/untitledEditorService'; +import 'vs/workbench/services/textfile/common/textResourcePropertiesService'; +import 'vs/workbench/services/mode/common/workbenchModeService'; +import 'vs/workbench/services/commands/common/commandService'; +import 'vs/workbench/services/themes/browser/workbenchThemeService'; +import 'vs/workbench/services/label/common/labelService'; +import 'vs/workbench/services/extensionManagement/common/extensionEnablementService'; +import 'vs/workbench/services/notification/common/notificationService'; + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; +import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IListService, ListService } from 'vs/platform/list/browser/listService'; +import { OpenerService } from 'vs/editor/browser/services/openerService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; +import { EditorWorkerServiceImpl } from 'vs/editor/common/services/editorWorkerServiceImpl'; +import { MarkerDecorationsService } from 'vs/editor/common/services/markerDecorationsServiceImpl'; +import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; +import { IMarkerService } from 'vs/platform/markers/common/markers'; +import { MarkerService } from 'vs/platform/markers/common/markerService'; +import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { TextResourceConfigurationService } from 'vs/editor/common/services/resourceConfigurationImpl'; +import { IMenuService } from 'vs/platform/actions/common/actions'; +import { MenuService } from 'vs/platform/actions/common/menuService'; +import { IDownloadService } from 'vs/platform/download/common/download'; +import { DownloadService } from 'vs/platform/download/common/downloadService'; + +registerSingleton(IExtensionGalleryService, ExtensionGalleryService, true); +registerSingleton(IContextViewService, ContextViewService, true); +registerSingleton(IListService, ListService, true); +registerSingleton(IOpenerService, OpenerService, true); +registerSingleton(IEditorWorkerService, EditorWorkerServiceImpl); +registerSingleton(IMarkerDecorationsService, MarkerDecorationsService); +registerSingleton(IMarkerService, MarkerService, true); +registerSingleton(IContextKeyService, ContextKeyService); +registerSingleton(IModelService, ModelServiceImpl, true); +registerSingleton(ITextResourceConfigurationService, TextResourceConfigurationService); +registerSingleton(IMenuService, MenuService, true); +registerSingleton(IDownloadService, DownloadService, true); + +//#endregion + + +//#region --- workbench contributions + +// Workspace File Watching +import 'vs/workbench/services/files/common/workspaceWatcher'; + +// Telemetry +import 'vs/workbench/contrib/telemetry/browser/telemetry.contribution'; + +// Preferences +import 'vs/workbench/contrib/preferences/browser/preferences.contribution'; +import 'vs/workbench/contrib/preferences/browser/keybindingsEditorContribution'; +import { IPreferencesSearchService } from 'vs/workbench/contrib/preferences/common/preferences'; +import { PreferencesSearchService } from 'vs/workbench/contrib/preferences/browser/preferencesSearch'; +registerSingleton(IPreferencesSearchService, PreferencesSearchService, true); + +// Logs +import 'vs/workbench/contrib/logs/common/logs.contribution'; + +// Quick Open Handlers +import 'vs/workbench/contrib/quickopen/browser/quickopen.contribution'; + +// Explorer +import 'vs/workbench/contrib/files/browser/explorerViewlet'; +import 'vs/workbench/contrib/files/browser/fileActions.contribution'; +import 'vs/workbench/contrib/files/browser/files.contribution'; + +// Backup +import 'vs/workbench/contrib/backup/common/backup.contribution'; + +// Search +import 'vs/workbench/contrib/search/browser/search.contribution'; +import 'vs/workbench/contrib/search/browser/searchView'; +import 'vs/workbench/contrib/search/browser/openAnythingHandler'; + +// SCM +import 'vs/workbench/contrib/scm/browser/scm.contribution'; +import 'vs/workbench/contrib/scm/browser/scmViewlet'; + +/* {{SQL CARBON EDIT}} // Debug +import 'vs/workbench/contrib/debug/browser/debug.contribution'; +import 'vs/workbench/contrib/debug/browser/debugQuickOpen'; +import 'vs/workbench/contrib/debug/browser/debugEditorContribution'; +import 'vs/workbench/contrib/debug/browser/repl'; +import 'vs/workbench/contrib/debug/browser/debugViewlet'; +*/ + +// Markers +import 'vs/workbench/contrib/markers/browser/markers.contribution'; + +// Comments +import 'vs/workbench/contrib/comments/browser/comments.contribution'; + +// URL Support +import 'vs/workbench/contrib/url/common/url.contribution'; + +// Webview +import 'vs/workbench/contrib/webview/browser/webview.contribution'; + +// Extensions Management +import 'vs/workbench/contrib/extensions/browser/extensions.contribution'; +import 'vs/workbench/contrib/extensions/browser/extensionsQuickOpen'; +import 'vs/workbench/contrib/extensions/browser/extensionsViewlet'; + +// Output Panel +import 'vs/workbench/contrib/output/browser/output.contribution'; +import 'vs/workbench/contrib/output/browser/outputPanel'; + +// Terminal +import 'vs/workbench/contrib/terminal/browser/terminal.contribution'; +import 'vs/workbench/contrib/terminal/browser/terminalQuickOpen'; +import 'vs/workbench/contrib/terminal/browser/terminalPanel'; + +// Relauncher +import 'vs/workbench/contrib/relauncher/common/relauncher.contribution'; + +// Tasks +// import 'vs/workbench/contrib/tasks/browser/task.contribution'; {{SQL CARBON EDIT}} + +// Remote +import 'vs/workbench/contrib/remote/common/remote.contribution'; + +// Emmet +// import 'vs/workbench/contrib/emmet/browser/emmet.contribution'; {{SQL CARBON EDIT}} + +// CodeEditor Contributions +import 'vs/workbench/contrib/codeEditor/browser/codeEditor.contribution'; + +// Execution +import 'vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution'; + +// Snippets +import 'vs/workbench/contrib/snippets/browser/snippets.contribution'; +import 'vs/workbench/contrib/snippets/browser/snippetsService'; +import 'vs/workbench/contrib/snippets/browser/insertSnippet'; +import 'vs/workbench/contrib/snippets/browser/configureSnippets'; +import 'vs/workbench/contrib/snippets/browser/tabCompletion'; + +// Formatter Help +import 'vs/workbench/contrib/format/browser/format.contribution'; + +// Themes +import 'vs/workbench/contrib/themes/browser/themes.contribution'; + +// Watermark +import 'vs/workbench/contrib/watermark/browser/watermark'; + +// Welcome +import 'vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution'; +import 'vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay'; + +// Call Hierarchy +import 'vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution'; + +// Outline +import 'vs/workbench/contrib/outline/browser/outline.contribution'; + +//#endregion diff --git a/src/vs/workbench/workbench.main.css b/src/vs/workbench/workbench.desktop.main.css similarity index 100% rename from src/vs/workbench/workbench.main.css rename to src/vs/workbench/workbench.desktop.main.css diff --git a/src/vs/workbench/workbench.main.nls.js b/src/vs/workbench/workbench.desktop.main.nls.js similarity index 100% rename from src/vs/workbench/workbench.main.nls.js rename to src/vs/workbench/workbench.desktop.main.nls.js diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.desktop.main.ts similarity index 64% rename from src/vs/workbench/workbench.main.ts rename to src/vs/workbench/workbench.desktop.main.ts index 52a5b19300..6e02c43b8f 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -3,72 +3,58 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -//#region --- workbench/editor core -import 'sql/setup'; // {{SQL CARBON EDIT}} -import 'vs/editor/editor.all'; -import 'vs/workbench/api/browser/extensionHost.contribution'; -import 'sql/workbench/api/electron-browser/extensionHost.contribution'; // {{SQL CARBON EDIT}} @anthonydresser add our extension contributions +// ####################################################################### +// ### ### +// ### !!! PLEASE ADD COMMON IMPORTS INTO WORKBENCH.COMMON.MAIN.TS !!! ### +// ### ### +// ####################################################################### + + +//#region --- workbench common + +import 'vs/workbench/workbench.common.main'; + +//#endregion + + +//#region --- workbench (desktop main) +import 'sql/setup'; // {{SQL CARBON EDIT}} import 'vs/workbench/electron-browser/main.contribution'; -import 'vs/workbench/browser/workbench.contribution'; - import 'vs/workbench/electron-browser/main'; //#endregion -//#region --- workbench actions - -import 'vs/workbench/browser/actions/layoutActions'; -import 'vs/workbench/browser/actions/windowActions'; -import 'vs/workbench/browser/actions/developerActions'; -import 'vs/workbench/browser/actions/listCommands'; -import 'vs/workbench/browser/actions/navigationActions'; -import 'vs/workbench/browser/parts/quickopen/quickOpenActions'; -import 'vs/workbench/browser/parts/quickinput/quickInputActions'; - -//#endregion - - -//#region --- API Extension Points - -import 'vs/workbench/api/common/menusExtensionPoint'; -import 'vs/workbench/api/common/configurationExtensionPoint'; -import 'vs/workbench/api/browser/viewsExtensionPoint'; - -//#endregion - - //#region --- workbench services +import 'vs/workbench/services/integrity/node/integrityService'; +import 'vs/workbench/services/textMate/electron-browser/textMateService'; +import 'vs/workbench/services/workspace/electron-browser/workspaceEditingService'; +import 'vs/workbench/services/extensions/common/inactiveExtensionUrlHandler'; +import 'vs/workbench/services/search/node/searchService'; +import 'vs/workbench/contrib/debug/electron-browser/extensionHostDebugService'; +import 'vs/workbench/services/output/node/outputChannelModelService'; +import 'vs/workbench/services/textfile/node/textFileService'; +import 'vs/workbench/services/dialogs/electron-browser/dialogService'; +import 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; +import 'vs/workbench/services/keybinding/electron-browser/keybinding.contribution'; +import 'vs/workbench/services/extensions/electron-browser/extensionService'; +import 'vs/workbench/services/contextmenu/electron-browser/contextmenuService'; +import 'vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService'; +import 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; +import 'vs/workbench/services/window/electron-browser/windowService'; +import 'vs/workbench/services/telemetry/electron-browser/telemetryService'; +import 'vs/workbench/services/configurationResolver/electron-browser/configurationResolverService'; +import 'vs/workbench/services/extensionManagement/node/extensionManagementService'; +import 'vs/workbench/services/accessibility/node/accessibilityService'; +import 'vs/workbench/services/remote/node/tunnelService'; +import 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService'; +import 'vs/workbench/services/backup/node/backupFileService'; + import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IMenuService } from 'vs/platform/actions/common/actions'; -import { MenuService } from 'vs/platform/actions/common/menuService'; -import { IListService, ListService } from 'vs/platform/list/browser/listService'; -import { OpenerService } from 'vs/editor/browser/services/openerService'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; -import { EditorWorkerServiceImpl } from 'vs/editor/common/services/editorWorkerServiceImpl'; -import { MarkerDecorationsService } from 'vs/editor/common/services/markerDecorationsServiceImpl'; -import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; -import { IMarkerService } from 'vs/platform/markers/common/markers'; -import { MarkerService } from 'vs/platform/markers/common/markerService'; -import { IDownloadService } from 'vs/platform/download/common/download'; -import { DownloadService } from 'vs/platform/download/common/downloadService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ClipboardService } from 'vs/platform/clipboard/electron-browser/clipboardService'; -import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; -import { TextResourceConfigurationService } from 'vs/editor/common/services/resourceConfigurationImpl'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { AccessibilityService } from 'vs/workbench/services/accessibility/node/accessibilityService'; -import { IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; -import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { IRequestService } from 'vs/platform/request/common/request'; import { RequestService } from 'vs/platform/request/browser/requestService'; import { LifecycleService } from 'vs/platform/lifecycle/electron-browser/lifecycleService'; @@ -90,71 +76,10 @@ import { IMenubarService } from 'vs/platform/menubar/common/menubar'; import { MenubarService } from 'vs/platform/menubar/electron-browser/menubarService'; import { IURLService } from 'vs/platform/url/common/url'; import { RelayURLService } from 'vs/platform/url/electron-browser/urlService'; -import { ITunnelService } from 'vs/platform/remote/common/tunnel'; -import { TunnelService } from 'vs/workbench/services/remote/node/tunnelService'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; -import 'vs/workbench/services/bulkEdit/browser/bulkEditService'; -import 'vs/workbench/services/integrity/node/integrityService'; -import 'vs/workbench/services/keybinding/common/keybindingEditing'; -import 'vs/workbench/services/textMate/electron-browser/textMateService'; -import 'vs/workbench/services/workspace/electron-browser/workspaceEditingService'; -import 'vs/workbench/services/extensions/common/inactiveExtensionUrlHandler'; -import 'vs/workbench/services/decorations/browser/decorationsService'; -import 'vs/workbench/services/search/node/searchService'; -import 'vs/workbench/services/progress/browser/progressService'; -import 'vs/workbench/services/editor/browser/codeEditorService'; -import 'vs/workbench/services/extensions/electron-browser/extensionHostDebugService'; -import 'vs/workbench/services/preferences/browser/preferencesService'; -import 'vs/workbench/services/output/node/outputChannelModelService'; -import 'vs/workbench/services/configuration/common/jsonEditingService'; -import 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; -import 'vs/workbench/services/textfile/node/textFileService'; -import 'vs/workbench/services/dialogs/browser/fileDialogService'; -import 'vs/workbench/services/dialogs/electron-browser/dialogService'; -import 'vs/workbench/services/editor/browser/editorService'; -import 'vs/workbench/services/history/browser/history'; -import 'vs/workbench/services/activity/browser/activityService'; -import 'vs/workbench/browser/parts/views/views'; -import 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; -import 'vs/workbench/services/keybinding/electron-browser/keybinding.contribution'; -import 'vs/workbench/services/keybinding/browser/keybindingService'; -import 'vs/workbench/services/untitled/common/untitledEditorService'; -import 'vs/workbench/services/textfile/common/textResourcePropertiesService'; -import 'vs/workbench/services/mode/common/workbenchModeService'; -import 'vs/workbench/services/commands/common/commandService'; -import 'vs/workbench/services/themes/browser/workbenchThemeService'; -import 'vs/workbench/services/extensions/electron-browser/extensionService'; -import 'vs/workbench/services/contextmenu/electron-browser/contextmenuService'; -import 'vs/workbench/services/label/common/labelService'; -import 'vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService'; -import 'vs/workbench/services/extensionManagement/common/extensionEnablementService'; -import 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; -import 'vs/workbench/services/notification/common/notificationService'; -import 'vs/workbench/services/window/electron-browser/windowService'; -import 'vs/workbench/services/telemetry/electron-browser/telemetryService'; -import 'vs/workbench/services/configurationResolver/electron-browser/configurationResolverService'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { BackupFileService } from 'vs/workbench/services/backup/node/backupFileService'; -import { ExtensionManagementService } from 'vs/workbench/services/extensionManagement/node/extensionManagementService'; - -registerSingleton(IExtensionManagementService, ExtensionManagementService); -registerSingleton(IBackupFileService, BackupFileService); -registerSingleton(IMenuService, MenuService, true); -registerSingleton(IListService, ListService, true); -registerSingleton(IOpenerService, OpenerService, true); -registerSingleton(IEditorWorkerService, EditorWorkerServiceImpl); -registerSingleton(IMarkerDecorationsService, MarkerDecorationsService); -registerSingleton(IMarkerService, MarkerService, true); -registerSingleton(IDownloadService, DownloadService, true); registerSingleton(IClipboardService, ClipboardService, true); -registerSingleton(IContextKeyService, ContextKeyService); -registerSingleton(IModelService, ModelServiceImpl, true); -registerSingleton(ITextResourceConfigurationService, TextResourceConfigurationService); -registerSingleton(IAccessibilityService, AccessibilityService, true); -registerSingleton(IContextViewService, ContextViewService, true); -registerSingleton(IExtensionGalleryService, ExtensionGalleryService, true); registerSingleton(IRequestService, RequestService, true); registerSingleton(ILifecycleService, LifecycleService); registerSingleton(ILocalizationsService, LocalizationsService); @@ -166,9 +91,7 @@ registerSingleton(IIssueService, IssueService); registerSingleton(IWorkspacesService, WorkspacesService); registerSingleton(IMenubarService, MenubarService); registerSingleton(IURLService, RelayURLService); -registerSingleton(ITunnelService, TunnelService, true); registerSingleton(ICredentialsService, KeytarCredentialsService, true); -registerSingleton(IWorkspaceStatsService, WorkspaceStatsService, true); //#endregion @@ -282,139 +205,40 @@ registerSingleton(ISchemaCompareService, SchemaCompareService); registerSingleton(IAdsTelemetryService, AdsTelemetryService); // {{SQL CARBON EDIT}} - End -//#region --- workbench parts - -import 'vs/workbench/browser/parts/quickinput/quickInput'; -import 'vs/workbench/browser/parts/quickopen/quickOpenController'; -import 'vs/workbench/browser/parts/titlebar/titlebarPart'; -import 'vs/workbench/browser/parts/editor/editorPart'; -import 'vs/workbench/browser/parts/activitybar/activitybarPart'; -import 'vs/workbench/browser/parts/panel/panelPart'; -import 'vs/workbench/browser/parts/sidebar/sidebarPart'; -import 'vs/workbench/browser/parts/statusbar/statusbarPart'; - -//#endregion - - //#region --- workbench contributions -// Workspace File Watching -import 'vs/workbench/services/files/common/workspaceWatcher'; - -// Telemetry -import 'vs/workbench/contrib/telemetry/browser/telemetry.contribution'; - // Localizations import 'vs/workbench/contrib/localizations/browser/localizations.contribution'; -// Preferences -import 'vs/workbench/contrib/preferences/browser/preferences.contribution'; -import 'vs/workbench/contrib/preferences/browser/keybindingsEditorContribution'; -import { IPreferencesSearchService } from 'vs/workbench/contrib/preferences/common/preferences'; -import { PreferencesSearchService } from 'vs/workbench/contrib/preferences/browser/preferencesSearch'; -registerSingleton(IPreferencesSearchService, PreferencesSearchService, true); - // Logs -import 'vs/workbench/contrib/logs/common/logs.contribution'; import 'vs/workbench/contrib/logs/electron-browser/logs.contribution'; -// Quick Open Handlers -import 'vs/workbench/contrib/quickopen/browser/quickopen.contribution'; - -// Explorer -import 'vs/workbench/contrib/files/browser/explorerViewlet'; -import 'vs/workbench/contrib/files/browser/fileActions.contribution'; -import 'vs/workbench/contrib/files/browser/files.contribution'; - -// Backup -import 'vs/workbench/contrib/backup/common/backup.contribution'; - // Stats import 'vs/workbench/contrib/stats/electron-browser/stats.contribution'; // Rapid Render Splash import 'vs/workbench/contrib/splash/electron-browser/partsSplash.contribution'; -// Search -import 'vs/workbench/contrib/search/browser/search.contribution'; -import 'vs/workbench/contrib/search/browser/searchView'; -import 'vs/workbench/contrib/search/browser/openAnythingHandler'; - -// SCM -import 'vs/workbench/contrib/scm/browser/scm.contribution'; -import 'vs/workbench/contrib/scm/browser/scmViewlet'; - -/* {{SQL CARBON EDIT}} // Debug -import 'vs/workbench/contrib/debug/browser/debug.contribution'; -import 'vs/workbench/contrib/debug/browser/debugQuickOpen'; -import 'vs/workbench/contrib/debug/browser/debugEditorContribution'; -import 'vs/workbench/contrib/debug/browser/repl'; -import 'vs/workbench/contrib/debug/browser/debugViewlet'; -import 'vs/workbench/contrib/debug/node/debugHelperService'; -*/ -// Markers -import 'vs/workbench/contrib/markers/browser/markers.contribution'; - -// Comments -import 'vs/workbench/contrib/comments/browser/comments.contribution'; - -// URL Support -import 'vs/workbench/contrib/url/common/url.contribution'; +// import 'vs/workbench/contrib/debug/node/debugHelperService'; {{SQL CARBON EDIT}} // Webview -import 'vs/workbench/contrib/webview/browser/webview.contribution'; import 'vs/workbench/contrib/webview/electron-browser/webview.contribution'; // Extensions Management -import 'vs/workbench/contrib/extensions/browser/extensions.contribution'; import 'vs/workbench/contrib/extensions/electron-browser/extensions.contribution'; -import 'vs/workbench/contrib/extensions/browser/extensionsQuickOpen'; -import 'vs/workbench/contrib/extensions/browser/extensionsViewlet'; - -// Output Panel -import 'vs/workbench/contrib/output/browser/output.contribution'; -import 'vs/workbench/contrib/output/browser/outputPanel'; // Terminal -import 'vs/workbench/contrib/terminal/browser/terminal.contribution'; import 'vs/workbench/contrib/terminal/electron-browser/terminal.contribution'; -import 'vs/workbench/contrib/terminal/browser/terminalQuickOpen'; -import 'vs/workbench/contrib/terminal/browser/terminalPanel'; - -// Relauncher -import 'vs/workbench/contrib/relauncher/electron-browser/relauncher.contribution'; - -// Tasks {{SQL CARBON EDIT}} remove tasks -// import 'vs/workbench/contrib/tasks/browser/task.contribution'; -// import { TaskService } from 'vs/workbench/contrib/tasks/electron-browser/taskService'; -// import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; -// registerSingleton(ITaskService, TaskService, true); // Remote -import 'vs/workbench/contrib/remote/common/remote.contribution'; import 'vs/workbench/contrib/remote/electron-browser/remote.contribution'; -// Emmet -// import 'vs/workbench/contrib/emmet/browser/emmet.contribution'; {{SQL CARBON EDIT}} @anthonydresser comment our emmet - // CodeEditor Contributions -import 'vs/workbench/contrib/codeEditor/browser/codeEditor.contribution'; import 'vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution'; // Execution import 'vs/workbench/contrib/externalTerminal/node/externalTerminalService'; -import 'vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution'; - -// Snippets -import 'vs/workbench/contrib/snippets/browser/snippets.contribution'; -import 'vs/workbench/contrib/snippets/browser/snippetsService'; -import 'vs/workbench/contrib/snippets/browser/insertSnippet'; -import 'vs/workbench/contrib/snippets/browser/configureSnippets'; -import 'vs/workbench/contrib/snippets/browser/tabCompletion'; - -// Formatter Help -import 'vs/workbench/contrib/format/browser/format.contribution'; // Send a Smile import 'vs/workbench/contrib/feedback/browser/feedback.contribution'; @@ -433,30 +257,20 @@ import 'vs/workbench/contrib/performance/electron-browser/performance.contributi import 'vs/workbench/contrib/cli/node/cli.contribution'; // Themes Support -import 'vs/workbench/contrib/themes/browser/themes.contribution'; import 'vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution'; -// Watermark -import 'vs/workbench/contrib/watermark/browser/watermark'; - // Welcome -import 'vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution'; import 'vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.contribution'; -import 'vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay'; import 'vs/workbench/contrib/welcome/page/browser/welcomePage.contribution'; -// Call Hierarchy -import 'vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution'; - -// Outline -import 'vs/workbench/contrib/outline/browser/outline.contribution'; - // Experiments import 'vs/workbench/contrib/experiments/electron-browser/experiments.contribution'; // Issues import 'vs/workbench/contrib/issue/electron-browser/issue.contribution'; -import { IWorkspaceStatsService, WorkspaceStatsService } from 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService'; + +// Tasks +import 'vs/workbench/contrib/tasks/electron-browser/taskService'; // {{SQL CARBON EDIT}} // SQL diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 30aea4e8ba..193d2fb2ba 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -7,7 +7,6 @@ import 'vs/workbench/workbench.web.main'; import { main } from 'vs/workbench/browser/web.main'; import { UriComponents } from 'vs/base/common/uri'; import { IFileSystemProvider } from 'vs/platform/files/common/files'; -import { IRequestOptions, IRequestContext } from 'vs/platform/request/common/request'; import { IWebSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory'; export interface IWorkbenchConstructionOptions { @@ -45,12 +44,6 @@ export interface IWorkbenchConstructionOptions { */ userDataProvider?: IFileSystemProvider; - /** - * Experimental: Optional request handler to handle http requests. - * In case not provided, workbench uses XMLHttpRequest. - */ - requestHandler?: (requestOptions: IRequestOptions) => Promise; - /** * A factory for web sockets. */ diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index ed6445c33c..b1d44d3599 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -3,357 +3,90 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -//#region --- workbench/editor core -import 'vs/editor/editor.all'; +// ####################################################################### +// ### ### +// ### !!! PLEASE ADD COMMON IMPORTS INTO WORKBENCH.COMMON.MAIN.TS !!! ### +// ### ### +// ####################################################################### -import 'vs/workbench/api/browser/extensionHost.contribution'; -import 'vs/workbench/browser/workbench.contribution'; +//#region --- workbench common + +import 'vs/workbench/workbench.common.main'; + +//#endregion + + +//#region --- workbench (web main) import 'vs/workbench/browser/web.main'; //#endregion -//#region --- workbench actions - -import 'vs/workbench/browser/actions/layoutActions'; -import 'vs/workbench/browser/actions/windowActions'; -import 'vs/workbench/browser/actions/developerActions'; -import 'vs/workbench/browser/actions/listCommands'; -import 'vs/workbench/browser/actions/navigationActions'; -import 'vs/workbench/browser/parts/quickopen/quickOpenActions'; -import 'vs/workbench/browser/parts/quickinput/quickInputActions'; - -//#endregion - - -//#region --- API Extension Points - -import 'vs/workbench/api/common/menusExtensionPoint'; -import 'vs/workbench/api/common/configurationExtensionPoint'; -import 'vs/workbench/api/browser/viewsExtensionPoint'; - -//#endregion - - //#region --- workbench services +import 'vs/workbench/services/textMate/browser/textMateService'; +import 'vs/workbench/services/search/common/searchService'; +import 'vs/workbench/services/output/common/outputChannelModelService'; +import 'vs/workbench/services/textfile/browser/textFileService'; +import 'vs/workbench/services/keybinding/browser/keymapService'; +import 'vs/workbench/services/extensions/browser/extensionService'; +import 'vs/workbench/services/extensionManagement/common/extensionManagementServerService'; +import 'vs/workbench/services/telemetry/browser/telemetryService'; +import 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; +import 'vs/workbench/browser/web.simpleservices'; + import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IMenuService } from 'vs/platform/actions/common/actions'; -import { MenuService } from 'vs/platform/actions/common/menuService'; -import { IListService, ListService } from 'vs/platform/list/browser/listService'; -import { OpenerService } from 'vs/editor/browser/services/openerService'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; -import { EditorWorkerServiceImpl } from 'vs/editor/common/services/editorWorkerServiceImpl'; -import { MarkerDecorationsService } from 'vs/editor/common/services/markerDecorationsServiceImpl'; -import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; -import { IMarkerService } from 'vs/platform/markers/common/markers'; -import { MarkerService } from 'vs/platform/markers/common/markerService'; -import { IDownloadService } from 'vs/platform/download/common/download'; -import { DownloadService } from 'vs/platform/download/common/downloadService'; +import { IRequestService } from 'vs/platform/request/common/request'; +import { RequestService } from 'vs/workbench/services/request/browser/requestService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { BrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService'; -import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; -import { TextResourceConfigurationService } from 'vs/editor/common/services/resourceConfigurationImpl'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { BrowserAccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; -import { IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; -import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; +import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { BrowserLifecycleService } from 'vs/platform/lifecycle/browser/lifecycleService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { DialogService } from 'vs/platform/dialogs/browser/dialogService'; -// import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; -// import { LocalizationsService } from 'vs/platform/localizations/electron-browser/localizationsService'; -// import { ISharedProcessService, SharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; -// import { IWindowsService } from 'vs/platform/windows/common/windows'; -// import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService'; -// import { IUpdateService } from 'vs/platform/update/common/update'; -// import { UpdateService } from 'vs/platform/update/electron-browser/updateService'; -// import { IIssueService } from 'vs/platform/issue/common/issue'; -// import { IssueService } from 'vs/platform/issue/electron-browser/issueService'; -// import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; -// import { WorkspacesService } from 'vs/platform/workspaces/electron-browser/workspacesService'; -// import { IMenubarService } from 'vs/platform/menubar/common/menubar'; -// import { MenubarService } from 'vs/platform/menubar/electron-browser/menubarService'; -// import { IURLService } from 'vs/platform/url/common/url'; -// import { RelayURLService } from 'vs/platform/url/electron-browser/urlService'; -// import { ITunnelService } from 'vs/platform/remote/common/tunnel'; -// import { TunnelService } from 'vs/workbench/services/remote/node/tunnelService'; -// import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; -// import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; -import 'vs/workbench/services/bulkEdit/browser/bulkEditService'; -// import 'vs/workbench/services/integrity/node/integrityService'; -import 'vs/workbench/services/keybinding/common/keybindingEditing'; -import 'vs/workbench/services/textMate/browser/textMateService'; -// import 'vs/workbench/services/workspace/electron-browser/workspaceEditingService'; -// import 'vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler'; -import 'vs/workbench/services/decorations/browser/decorationsService'; -import 'vs/workbench/services/search/common/searchService'; -import 'vs/workbench/services/progress/browser/progressService'; -import 'vs/workbench/services/editor/browser/codeEditorService'; -// import 'vs/workbench/services/extensions/electron-browser/extensionHostDebugService'; -import 'vs/workbench/services/preferences/browser/preferencesService'; -import 'vs/workbench/services/output/common/outputChannelModelService'; -import 'vs/workbench/services/configuration/common/jsonEditingService'; -import 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; -import 'vs/workbench/services/textfile/browser/textFileService'; -import 'vs/workbench/services/dialogs/browser/fileDialogService'; -// import 'vs/workbench/services/dialogs/electron-browser/dialogService'; -import 'vs/workbench/services/editor/browser/editorService'; -import 'vs/workbench/services/history/browser/history'; -import 'vs/workbench/services/activity/browser/activityService'; -import 'vs/workbench/browser/parts/views/views'; -import 'vs/workbench/services/keybinding/browser/keymapService'; -import 'vs/workbench/services/keybinding/browser/keybindingService'; -import 'vs/workbench/services/untitled/common/untitledEditorService'; -import 'vs/workbench/services/textfile/common/textResourcePropertiesService'; -import 'vs/workbench/services/mode/common/workbenchModeService'; -import 'vs/workbench/services/commands/common/commandService'; -import 'vs/workbench/services/themes/browser/workbenchThemeService'; -import 'vs/workbench/services/extensions/browser/extensionService'; -// import 'vs/workbench/services/contextmenu/electron-browser/contextmenuService'; -import 'vs/workbench/services/label/common/labelService'; -import 'vs/workbench/services/extensionManagement/common/extensionManagementServerService'; -import 'vs/workbench/services/extensionManagement/common/extensionEnablementService'; -// import 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; -import 'vs/workbench/services/notification/common/notificationService'; -// import 'vs/workbench/services/window/electron-browser/windowService'; -import 'vs/workbench/services/telemetry/browser/telemetryService'; -import 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; -import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { BackupFileService } from 'vs/workbench/services/backup/common/backupFileService'; import { ExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagementService'; -import 'vs/workbench/browser/web.simpleservices'; - +registerSingleton(IRequestService, RequestService, true); registerSingleton(IExtensionManagementService, ExtensionManagementService); registerSingleton(IBackupFileService, BackupFileService); registerSingleton(IDialogService, DialogService, true); -registerSingleton(IMenuService, MenuService, true); -registerSingleton(IListService, ListService, true); -registerSingleton(IOpenerService, OpenerService, true); -registerSingleton(IEditorWorkerService, EditorWorkerServiceImpl); -registerSingleton(IMarkerDecorationsService, MarkerDecorationsService); -registerSingleton(IMarkerService, MarkerService, true); -registerSingleton(IDownloadService, DownloadService, true); registerSingleton(IClipboardService, BrowserClipboardService, true); -registerSingleton(IContextKeyService, ContextKeyService); -registerSingleton(IModelService, ModelServiceImpl, true); -registerSingleton(ITextResourceConfigurationService, TextResourceConfigurationService); registerSingleton(IAccessibilityService, BrowserAccessibilityService, true); -registerSingleton(IContextViewService, ContextViewService, true); -registerSingleton(IExtensionGalleryService, ExtensionGalleryService, true); registerSingleton(ILifecycleService, BrowserLifecycleService); -// registerSingleton(ILocalizationsService, LocalizationsService); -// registerSingleton(ISharedProcessService, SharedProcessService, true); -// registerSingleton(IWindowsService, WindowsService); -// registerSingleton(IUpdateService, UpdateService); -// registerSingleton(IIssueService, IssueService); -// registerSingleton(IWorkspacesService, WorkspacesService); -// registerSingleton(IMenubarService, MenubarService); -// registerSingleton(IURLService, RelayURLService); -// registerSingleton(ITunnelService, TunnelService, true); -// registerSingleton(ICredentialsService, KeytarCredentialsService, true); registerSingleton(IContextMenuService, ContextMenuService); //#endregion -//#region --- workbench parts - -import 'vs/workbench/browser/parts/quickinput/quickInput'; -import 'vs/workbench/browser/parts/quickopen/quickOpenController'; -import 'vs/workbench/browser/parts/titlebar/titlebarPart'; -import 'vs/workbench/browser/parts/editor/editorPart'; -import 'vs/workbench/browser/parts/activitybar/activitybarPart'; -import 'vs/workbench/browser/parts/panel/panelPart'; -import 'vs/workbench/browser/parts/sidebar/sidebarPart'; -import 'vs/workbench/browser/parts/statusbar/statusbarPart'; - -//#endregion - - //#region --- workbench contributions // Resource Service Worker import 'vs/workbench/contrib/resources/browser/resourceServiceWorkerClient'; -// Workspace File Watching -import 'vs/workbench/services/files/common/workspaceWatcher'; - -// Telemetry -import 'vs/workbench/contrib/telemetry/browser/telemetry.contribution'; - -// Localizations -// import 'vs/workbench/contrib/localizations/browser/localizations.contribution'; - // Preferences -import 'vs/workbench/contrib/preferences/browser/preferences.contribution'; -import 'vs/workbench/contrib/preferences/browser/keybindingsEditorContribution'; import 'vs/workbench/contrib/preferences/browser/keyboardLayoutPicker'; -import { IPreferencesSearchService } from 'vs/workbench/contrib/preferences/common/preferences'; -import { PreferencesSearchService } from 'vs/workbench/contrib/preferences/browser/preferencesSearch'; -registerSingleton(IPreferencesSearchService, PreferencesSearchService, true); - -// Logs -import 'vs/workbench/contrib/logs/common/logs.contribution'; - -// Quick Open Handlers -import 'vs/workbench/contrib/quickopen/browser/quickopen.contribution'; - -// Explorer -import 'vs/workbench/contrib/files/browser/explorerViewlet'; -import 'vs/workbench/contrib/files/browser/fileActions.contribution'; -import 'vs/workbench/contrib/files/browser/files.contribution'; - -// Backup -import 'vs/workbench/contrib/backup/common/backup.contribution'; - -// Stats -// import 'vs/workbench/contrib/stats/electron-browser/stats.contribution'; - -// Rapid Render Splash -// import 'vs/workbench/contrib/splash/electron-browser/partsSplash.contribution'; - -// Search -import 'vs/workbench/contrib/search/browser/search.contribution'; -import 'vs/workbench/contrib/search/browser/searchView'; -import 'vs/workbench/contrib/search/browser/openAnythingHandler'; - -// SCM -import 'vs/workbench/contrib/scm/browser/scm.contribution'; -import 'vs/workbench/contrib/scm/browser/scmViewlet'; // Debug -import 'vs/workbench/contrib/debug/browser/debug.contribution'; -import 'vs/workbench/contrib/debug/browser/debugQuickOpen'; -import 'vs/workbench/contrib/debug/browser/debugEditorContribution'; -import 'vs/workbench/contrib/debug/browser/repl'; -import 'vs/workbench/contrib/debug/browser/debugViewlet'; -import 'vs/workbench/contrib/debug/browser/debugHelperService'; - -// Markers -import 'vs/workbench/contrib/markers/browser/markers.contribution'; - -// Comments -import 'vs/workbench/contrib/comments/browser/comments.contribution'; - -// URL Support -import 'vs/workbench/contrib/url/common/url.contribution'; +import 'vs/workbench/contrib/debug/browser/extensionHostDebugService'; // Webview -import 'vs/workbench/contrib/webview/browser/webview.contribution'; - -import { IWebviewService } from 'vs/workbench/contrib/webview/common/webview'; -import { WebviewService } from 'vs/workbench/contrib/webview/browser/webviewService'; -import { IWebviewEditorService, WebviewEditorService } from 'vs/workbench/contrib/webview/browser/webviewEditorService'; -registerSingleton(IWebviewService, WebviewService, true); -registerSingleton(IWebviewEditorService, WebviewEditorService, true); - -// Extensions Management -import 'vs/workbench/contrib/extensions/browser/extensions.contribution'; -import 'vs/workbench/contrib/extensions/browser/extensionsQuickOpen'; -import 'vs/workbench/contrib/extensions/browser/extensionsViewlet'; - -// Output Panel -import 'vs/workbench/contrib/output/browser/output.contribution'; -import 'vs/workbench/contrib/output/browser/outputPanel'; +import 'vs/workbench/contrib/webview/browser/webviewService'; +import 'vs/workbench/contrib/webview/browser/webviewEditorService'; // Terminal -import 'vs/workbench/contrib/terminal/browser/terminal.contribution'; -// import 'vs/workbench/contrib/terminal/electron-browser/terminal.contribution'; -import 'vs/workbench/contrib/terminal/browser/terminalQuickOpen'; -import 'vs/workbench/contrib/terminal/browser/terminalPanel'; - -import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { ITerminalNativeService } from 'vs/workbench/contrib/terminal/common/terminal'; -import { TerminalNativeService } from 'vs/workbench/contrib/terminal/browser/terminalNativeService'; -import { TerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminalInstanceService'; -registerSingleton(ITerminalNativeService, TerminalNativeService, true); -registerSingleton(ITerminalInstanceService, TerminalInstanceService, true); - -// Relauncher -// import 'vs/workbench/contrib/relauncher/electron-browser/relauncher.contribution'; +import 'vs/workbench/contrib/terminal/browser/terminalNativeService'; +import 'vs/workbench/contrib/terminal/browser/terminalInstanceService'; // Tasks -import 'vs/workbench/contrib/tasks/browser/task.contribution'; -import { TaskService } from 'vs/workbench/contrib/tasks/browser/taskService'; -import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; -registerSingleton(ITaskService, TaskService, true); - -// Remote -import 'vs/workbench/contrib/remote/common/remote.contribution'; -// import 'vs/workbench/contrib/remote/electron-browser/remote.contribution'; - -// Emmet -import 'vs/workbench/contrib/emmet/browser/emmet.contribution'; - -// CodeEditor Contributions -import 'vs/workbench/contrib/codeEditor/browser/codeEditor.contribution'; -// import 'vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution'; - -// External terminal -import 'vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution'; - -// Snippets -import 'vs/workbench/contrib/snippets/browser/snippets.contribution'; -import 'vs/workbench/contrib/snippets/browser/snippetsService'; -import 'vs/workbench/contrib/snippets/browser/insertSnippet'; -import 'vs/workbench/contrib/snippets/browser/configureSnippets'; -import 'vs/workbench/contrib/snippets/browser/tabCompletion'; - -// Formatter Help -import 'vs/workbench/contrib/format/browser/format.contribution'; - -// Send a Smile -// import 'vs/workbench/contrib/feedback/browser/feedback.contribution'; - -// Update -// import 'vs/workbench/contrib/update/electron-browser/update.contribution'; - -// Surveys -// import 'vs/workbench/contrib/surveys/electron-browser/nps.contribution'; -// import 'vs/workbench/contrib/surveys/electron-browser/languageSurveys.contribution'; - -// Performance -// import 'vs/workbench/contrib/performance/electron-browser/performance.contribution'; - -// CLI -// import 'vs/workbench/contrib/cli/node/cli.contribution'; - -// Themes Support -import 'vs/workbench/contrib/themes/browser/themes.contribution'; -// import 'vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution'; - -// Watermark -import 'vs/workbench/contrib/watermark/browser/watermark'; - -// Welcome -import 'vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution'; -// import 'vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.contribution'; -import 'vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay'; -// import 'vs/workbench/contrib/welcome/page/browser/welcomePage.contribution'; - -// Call Hierarchy -import 'vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution'; - -// Outline -import 'vs/workbench/contrib/outline/browser/outline.contribution'; - -// Experiments -// import 'vs/workbench/contrib/experiments/electron-browser/experiments.contribution'; - -// Issues -// import 'vs/workbench/contrib/issue/electron-browser/issue.contribution'; +import 'vs/workbench/contrib/tasks/browser/taskService'; //#endregion diff --git a/test/smoke/package.json b/test/smoke/package.json index 11b5662b20..19378e70a5 100644 --- a/test/smoke/package.json +++ b/test/smoke/package.json @@ -22,7 +22,7 @@ "@types/webdriverio": "4.6.1", "concurrently": "^3.5.1", "cpx": "^1.5.0", - "electron": "4.2.7", + "electron": "4.2.9", "htmlparser2": "^3.9.2", "mkdirp": "^0.5.1", "mocha": "^5.2.0", diff --git a/test/smoke/src/areas/workbench/data-migration.test.ts b/test/smoke/src/areas/workbench/data-migration.test.ts index 02f5d6f5bd..1c7f85c100 100644 --- a/test/smoke/src/areas/workbench/data-migration.test.ts +++ b/test/smoke/src/areas/workbench/data-migration.test.ts @@ -39,7 +39,7 @@ export function setup(stableCodePath: string, testDataPath: string) { insiderOptions.userDataDir = userDataDir; const insidersApp = new Application(insiderOptions); - await insidersApp!.start(false /* not expecting walkthrough parth */); + await insidersApp!.start(false /* not expecting walkthrough path */); // Verify 3 editors are open await insidersApp.workbench.editors.waitForEditorFocus('Untitled-1'); @@ -66,7 +66,7 @@ export function setup(stableCodePath: string, testDataPath: string) { await stableApp.workbench.editors.newUntitledFile(); const untitled = 'Untitled-1'; - const textToTypeInUntitled = 'Hello, Unitled Code'; + const textToTypeInUntitled = 'Hello, Untitled Code'; await stableApp.workbench.editor.waitForTypeInEditor(untitled, textToTypeInUntitled); const readmeMd = 'readme.md'; @@ -80,7 +80,7 @@ export function setup(stableCodePath: string, testDataPath: string) { insiderOptions.userDataDir = userDataDir; const insidersApp = new Application(insiderOptions); - await insidersApp!.start(false /* not expecting walkthrough parth */); + await insidersApp!.start(false /* not expecting walkthrough path */); await insidersApp.workbench.editors.waitForActiveTab(readmeMd, true); await insidersApp.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1); @@ -92,4 +92,4 @@ export function setup(stableCodePath: string, testDataPath: string) { await insidersApp.stop(); }); }); -} \ No newline at end of file +} diff --git a/test/smoke/yarn.lock b/test/smoke/yarn.lock index b86356d3ac..3935a9baa4 100644 --- a/test/smoke/yarn.lock +++ b/test/smoke/yarn.lock @@ -676,10 +676,10 @@ electron-download@^4.1.0: semver "^5.4.1" sumchecker "^2.0.2" -electron@4.2.7: - version "4.2.7" - resolved "https://registry.yarnpkg.com/electron/-/electron-4.2.7.tgz#bdd2dbf489a4a4255405bd8330cc8509831d29ba" - integrity sha512-Azpkw0OPzKVipSsN9/0DrBQhXOpG48Q1gTG7Akchtv37s8TijMe403TUgHxGGhw2ti117ek51kYf7NXLhjXqoA== +electron@4.2.9: + version "4.2.9" + resolved "https://registry.yarnpkg.com/electron/-/electron-4.2.9.tgz#81226aa1ba58e1b05388474faf5a815010a11ea2" + integrity sha512-zC7K3GOiZKmxqllVG/qq/Gx+qQvyolKj5xKKwXMqIGekfokEW2hvoIO5Yh7KCoAh5dqBtpzOJjS4fj1se+YBcg== dependencies: "@types/node" "^10.12.18" electron-download "^4.1.0" diff --git a/yarn.lock b/yarn.lock index 2ff20de298..1026a4eed4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -145,6 +145,69 @@ lodash "^4.17.11" to-fast-properties "^2.0.0" +"@microsoft/applicationinsights-analytics-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-2.1.1.tgz#6d09c1915f808026e2d45165d04802f09affed59" + integrity sha512-VKIutoFKY99CyKwxLUuj6Vnq14/QwXo9/QSQDpYnHEjo+uKn7QmLsHqWw0K9uYNfNAXt4BZimX/zDg6jZtzeXg== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-channel-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.1.1.tgz#e205eddd93e49d17d9e0711a612b4bfc9810888f" + integrity sha512-fYr9IAqtaEr9AmaPaL3SLQVT3t3GQzl+n74gpNKyAVakDIm0nYQ/bimjdcAhJMDf1VGNSPg/xICneyuZg7Wxlg== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-common@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-common/-/applicationinsights-common-2.1.1.tgz#27e6074584a7a3a8ca3f11f7ff2b7ff0f395bf2d" + integrity sha512-2hkS1Ia1FmAjCuYZ5JlG20/WgObqdsKtmK5YALAFGHIB4KSQ/Za1qazS+7GsG+E0F9UJivNWL1geUIcNqg5Qjg== + dependencies: + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-core-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.1.1.tgz#30fb6a519cc1c6119c419c4811ce72c260217d9e" + integrity sha512-4t4wf6SKqIcWEQDPg/uOhm+BxtHhu/AFreyEoYZmMfcxzAu33h1FtTQRtxBNbYH1+thiNZCh80yUpnT7d9Hrlw== + dependencies: + tslib "^1.9.3" + +"@microsoft/applicationinsights-dependencies-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-2.1.1.tgz#8154c3efcb24617d015d0bce7c2cc47797a8d3c4" + integrity sha512-yhb4EToBp+aI+qLo0h5NDNtoo3sDFV60uyIOK843YjzXqVotcXX/lRShlghTkJtYH09QhrdzDjViUHnD4sMFSQ== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-properties-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-2.1.1.tgz#ca34232766eb16167b5d87693e2ae5d94f2a1559" + integrity sha512-8l+/ppw6xKTam2RL4EHZ52Lcf217olw81j6kyBNKtIcGwSnLNHrFwEeF3vBWIteG2JKzlg1GhGjrkB3oxXsV2g== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-web@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web/-/applicationinsights-web-2.1.1.tgz#1a44eddda7c244b88d9eb052dab6c855682e4f05" + integrity sha512-crvhCkNsNxkFuPWmttyWNSAA96D5FxBtKS6UA9MV9f9XHevTfchf/E3AuU9JZcsXufWMQLwLrUQ9ZiA1QJ0EWA== + dependencies: + "@microsoft/applicationinsights-analytics-js" "2.1.1" + "@microsoft/applicationinsights-channel-js" "2.1.1" + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + "@microsoft/applicationinsights-dependencies-js" "2.1.1" + "@microsoft/applicationinsights-properties-js" "2.1.1" + "@types/chart.js@^2.7.31": version "2.7.48" resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.7.48.tgz#db7b6d6ed33659f97ee49181f22c980bb0790a7b" @@ -179,11 +242,6 @@ resolved "https://registry.yarnpkg.com/@types/keytar/-/keytar-4.4.0.tgz#ca24e6ee6d0df10c003aafe26e93113b8faf0d8e" integrity sha512-cq/NkUUy6rpWD8n7PweNQQBpw2o0cf5v6fbkUVEpOB9VzzIvyPvSEId1/goIj+MciW2v1Lw5mRimKO01XgE9EA== -"@types/minimist@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6" - integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= - "@types/mocha@2.2.39": version "2.2.39" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.39.tgz#f68d63db8b69c38e9558b4073525cf96c4f7a829" @@ -6134,11 +6192,6 @@ nan@2.14.0, nan@^2.0.0, nan@^2.13.2, nan@^2.14.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== -nan@^2.12.1: - version "2.12.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552" - integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw== - nan@^2.9.2: version "2.10.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" @@ -6166,10 +6219,10 @@ napi-build-utils@^1.0.1: resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.1.tgz#1381a0f92c39d66bf19852e7873432fc2123e508" integrity sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA== -native-is-elevated@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/native-is-elevated/-/native-is-elevated-0.2.1.tgz#70a2123a8575b9f624a3ef465d98cb74ae017385" - integrity sha1-cKISOoV1ufYko+9GXZjLdK4Bc4U= +native-is-elevated@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/native-is-elevated/-/native-is-elevated-0.3.0.tgz#6c5d8f57daeec129abd03b5606a55e56e4337423" + integrity sha512-QJgU7vaCZ199PSEC4LAmwtGfqwGaz8a51YDeze3DPiRzcOq25LIQpxCbBWunIu+csMMHFsDuyO2OVfeSD4ioHQ== native-keymap@2.0.0: version "2.0.0" @@ -9286,6 +9339,11 @@ tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== +tslib@^1.9.3: + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + tslint-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" @@ -9892,6 +9950,11 @@ vscode-fsevents@1.2.12: dependencies: nan "^2.14.0" +vscode-minimist@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/vscode-minimist/-/vscode-minimist-1.2.1.tgz#e63d3f4a9bf3680dcb8f9304eed612323fd6926a" + integrity sha512-cmB72+qDoiCFJ1UKnGUBdGYfXzdpJ3bQM/D/+XhkVk5v7uZgLbYiCz5JcwVyk7NC7hSi5VGtQ4wihzmi12NeXw== + vscode-nls-dev@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/vscode-nls-dev/-/vscode-nls-dev-3.3.1.tgz#15fc03e0c9ca5a150abb838690d9554ac06f77e4" @@ -9920,10 +9983,10 @@ vscode-proxy-agent@0.4.0: https-proxy-agent "2.2.1" socks-proxy-agent "4.0.1" -vscode-ripgrep@^1.5.5: - version "1.5.5" - resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.5.tgz#24c0e9cb356cf889c98e15ecb58f9cf654a1d961" - integrity sha512-OrPrAmcun4+uZAuNcQvE6CCPskh+5AsjANod/Q3zRcJcGNxgoOSGlQN9RPtatkUNmkN8Nn8mZBnS1jMylu/dKg== +vscode-ripgrep@^1.5.6: + version "1.5.6" + resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.6.tgz#93bf5c99ca5f8248950a305e224f6ca153c30af4" + integrity sha512-WRIM9XpUj6dsfdAmuI3ANbmT1ysPUVsYy/2uCLDHJa9kbiB4T7uGvFnnc0Rgx2qQnyRAwL7PeWaFgUljPPxf2g== vscode-sqlite3@4.0.8: version "4.0.8" @@ -9946,12 +10009,10 @@ vscode-windows-ca-certs@0.1.0: dependencies: node-addon-api "1.6.2" -vscode-windows-registry@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/vscode-windows-registry/-/vscode-windows-registry-1.0.1.tgz#bc9f765563eb6dc1c9ad9a41f9eaacc84dfadc7c" - integrity sha512-q0aKXi9Py1OBdmXIJJFeJBzpPJMMUxMJNBU9FysWIXEwJyMQGEVevKzM2J3Qz/cHSc5LVqibmoUWzZ7g+97qRg== - dependencies: - nan "^2.12.1" +vscode-windows-registry@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/vscode-windows-registry/-/vscode-windows-registry-1.0.2.tgz#b863e704a6a69c50b3098a55fbddbe595b0c124a" + integrity sha512-/CLLvuOSM2Vme2z6aNyB+4Omd7hDxpf4Thrt8ImxnXeQtxzel2bClJpFQvQqK/s4oaXlkBKS7LqVLeZM+uSVIA== vso-node-api@6.1.2-preview: version "6.1.2-preview" @@ -10236,10 +10297,10 @@ xterm-addon-web-links@0.1.0-beta10: resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.1.0-beta10.tgz#610fa9773a2a5ccd41c1c83ba0e2dd2c9eb66a23" integrity sha512-xfpjy0V6bB4BR44qIgZQPoCMVakxb65gMscPkHpO//QxvUxKzabV3dxOsIbeZRFkUGsWTFlvz2OoaBLoNtv5gg== -xterm@3.15.0-beta90: - version "3.15.0-beta90" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta90.tgz#e1732c2914584c86cffa797ba762c482f21ce182" - integrity sha512-eixIA5brfoez+Y8bJPCcIw8Q7LgOvxRX3cPBaGmo7ozUASx9IEGOmvIRQX1ozTUmxEiXPnAxOfl/isQ+yNlaww== +xterm@3.15.0-beta98: + version "3.15.0-beta98" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta98.tgz#37f37c35577422880e7ef673cc37f9d2a45dd40c" + integrity sha512-vZbg2LcRvoiJOgr1MyeLFM9mF4uib3BWUWDHyFc+vZ58CTuK0iczOvFXgk/ySo23ZLqwmHQSigLgmWvZ8J5G0Q== y18n@^3.2.1: version "3.2.1"