From ede827ee823f8613ce84b11eb676f7e6752c231c Mon Sep 17 00:00:00 2001 From: ADS Merger Date: Thu, 23 Jul 2020 02:21:09 +0000 Subject: [PATCH] Merge from vscode 8c426f9f3b6b18935cc6c2ec8aa6d45ccd88021e --- build/package.json | 2 +- build/yarn.lock | 8 +- extensions/git/package.json | 2 +- extensions/git/src/main.ts | 2 +- extensions/package.json | 2 +- extensions/yarn.lock | 8 +- scripts/test-integration.bat | 6 +- scripts/test-integration.sh | 2 +- src/vs/base/browser/contextmenu.ts | 2 +- .../base/browser/ui/actionbar/actionbar.css | 1 - .../base/browser/ui/codicons/codiconStyles.ts | 2 +- .../browser/ui/contextview/contextview.ts | 93 ++- src/vs/base/browser/ui/dropdown/dropdown.ts | 12 +- src/vs/base/browser/ui/menu/menu.css | 225 -------- src/vs/base/browser/ui/menu/menu.ts | 317 ++++++++++- src/vs/base/browser/ui/menu/menubar.css | 83 +++ src/vs/base/browser/ui/menu/menubar.ts | 1 + src/vs/base/common/marshalling.ts | 24 +- src/vs/base/parts/ipc/common/ipc.net.ts | 14 + src/vs/base/parts/ipc/common/ipc.ts | 22 +- src/vs/base/parts/ipc/node/ipc.net.ts | 42 +- .../contrib/codeAction/codeActionMenu.ts | 1 + .../editor/contrib/contextmenu/contextmenu.ts | 2 + src/vs/editor/contrib/dnd/dnd.ts | 1 + src/vs/editor/contrib/find/findWidget.css | 2 +- .../contextview/browser/contextMenuHandler.ts | 4 +- .../contextview/browser/contextView.ts | 2 +- .../contextview/browser/contextViewService.ts | 14 +- .../platform/extensions/common/extensions.ts | 21 +- .../remote/browser/browserSocketFactory.ts | 3 + .../undoRedo/common/undoRedoService.ts | 123 +++- .../common/userDataAutoSyncService.ts | 6 + src/vs/vscode.proposed.d.ts | 16 - .../api/browser/mainThreadDebugService.ts | 8 - .../workbench/api/browser/mainThreadEditor.ts | 22 +- .../api/browser/mainThreadExtensionService.ts | 2 +- .../workbench/api/common/extHost.protocol.ts | 3 +- .../api/common/extHostDebugService.ts | 4 - .../api/common/extHostExtensionService.ts | 26 +- .../workbench/api/common/extHostRpcService.ts | 4 +- .../api/worker/extHostExtensionService.ts | 5 +- .../parts/activitybar/activitybarActions.ts | 15 +- .../parts/activitybar/activitybarPart.ts | 63 +- src/vs/workbench/browser/viewlet.ts | 8 +- .../contrib/debug/common/debugModel.ts | 2 +- .../extensions/browser/extensionsActions.ts | 44 ++ .../extensions/browser/extensionsViewlet.ts | 537 +++++++++--------- .../extensions/browser/extensionsViews.ts | 44 +- .../contrib/notebook/browser/constants.ts | 3 +- .../notebook/browser/contrib/coreActions.ts | 47 +- .../browser/contrib/status/editorStatus.ts | 15 +- .../notebook/browser/media/notebook.css | 28 +- .../notebook/browser/notebookBrowser.ts | 7 + .../notebook/browser/notebookEditorWidget.ts | 164 ++++-- .../view/renderers/backLayerWebView.ts | 14 +- .../browser/view/renderers/cellActionView.ts | 74 +++ .../browser/view/renderers/cellMenus.ts | 9 - .../browser/view/renderers/cellRenderer.ts | 107 ++-- .../browser/view/renderers/markdownCell.ts | 4 +- .../browser/viewModel/codeCellViewModel.ts | 4 +- .../viewModel/markdownCellViewModel.ts | 8 +- .../contrib/notebook/common/model/cellEdit.ts | 18 +- .../common/model/notebookTextModel.ts | 69 ++- .../notebook/test/testNotebookEditor.ts | 6 + .../tasks/browser/abstractTaskService.ts | 12 +- .../terminal/browser/terminalInstance.ts | 3 + .../contrib/terminal/browser/terminalView.ts | 2 + .../terminal/browser/widgets/widgetManager.ts | 8 + ...lSyncView.ts => userDataSyncMergesView.ts} | 18 +- .../userDataSync/browser/userDataSyncViews.ts | 18 +- .../services/editor/common/editorOpenWith.ts | 7 +- .../extensions/common/extensionHostManager.ts | 1 + .../extensions/common/proxyIdentifier.ts | 5 + .../services/extensions/common/rpcProtocol.ts | 7 + .../node/extensionHostProcessSetup.ts | 10 +- .../browser/userDataSyncWorkbenchService.ts | 20 +- .../userDataSync/common/userDataSync.ts | 4 +- .../browser/api/extHostDiagnostics.test.ts | 6 + .../api/extHostFileSystemEventService.test.ts | 3 +- .../test/browser/api/extHostWorkspace.test.ts | 3 +- .../browser/api/mainThreadDiagnostics.test.ts | 1 + .../browser/api/mainThreadTreeViews.test.ts | 1 + .../test/browser/api/testRPCProtocol.ts | 7 +- 83 files changed, 1736 insertions(+), 829 deletions(-) delete mode 100644 src/vs/base/browser/ui/menu/menu.css create mode 100644 src/vs/base/browser/ui/menu/menubar.css create mode 100644 src/vs/workbench/contrib/notebook/browser/view/renderers/cellActionView.ts rename src/vs/workbench/contrib/userDataSync/browser/{userDataManualSyncView.ts => userDataSyncMergesView.ts} (97%) diff --git a/build/package.json b/build/package.json index 23d1948000..7e813a381b 100644 --- a/build/package.json +++ b/build/package.json @@ -49,7 +49,7 @@ "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-node-resolve": "^5.2.0", "terser": "4.3.8", - "typescript": "^4.0.0-dev.20200715", + "typescript": "^4.0.0-dev.20200722", "vsce": "1.48.0", "vscode-telemetry-extractor": "^1.6.0", "xml2js": "^0.4.17" diff --git a/build/yarn.lock b/build/yarn.lock index 01a79de5d4..c1d6b399dd 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -3539,10 +3539,10 @@ typescript@^3.0.1: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== -typescript@^4.0.0-dev.20200715: - version "4.0.0-dev.20200715" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.0-dev.20200715.tgz#d65961a5a6f13fde95a6f4db5f5946f15e4c59bc" - integrity sha512-gmPXoWktfXeutmWTM6el9U4vIn5kqOHGI1OESSOhPtLWrxodKqLfFuMygQtOUTtGjKLFQRFAJhHEwUhHZNOURA== +typescript@^4.0.0-dev.20200722: + version "4.0.0-dev.20200722" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.0-dev.20200722.tgz#b59dd5a3cd84a98d5aae0e4f3a3c58f0c81a3b9b" + integrity sha512-MmJ1YyPNK3JYeKLiTg5sQXdeZaMgt99Fg4BMRZhJmhoq1/x2V1cpXMYvE1rtIYl9K7NvmTDdU3WDW7ZOD6ybaw== typical@^4.0.0: version "4.0.0" diff --git a/extensions/git/package.json b/extensions/git/package.json index e96ef36aba..d9df45a10b 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1801,7 +1801,7 @@ "Ignore", "ignore" ], - "filenames": [ + "extensions": [ ".gitignore_global", ".gitignore" ], diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 8102413024..16ac8a51cc 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -74,7 +74,7 @@ async function createModel(context: ExtensionContext, outputChannel: OutputChann new GitTimelineProvider(model) ); - // await checkGitVersion(info); {{SQL CARBON EDIT}} Don't check git version + // checkGitVersion(info); {{SQL CARBON EDIT}} Don't check git version return model; } diff --git a/extensions/package.json b/extensions/package.json index 7c668c9744..69f5ea275b 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.9.6" + "typescript": "3.9.7" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/yarn.lock b/extensions/yarn.lock index d41a4ab48b..86223e7721 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -76,10 +76,10 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -typescript@3.9.6: - version "3.9.6" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a" - integrity sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw== +typescript@3.9.7: + version "3.9.7" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" + integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== wrappy@1: version "1.0.2" diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index 1396749802..52513c6c22 100755 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -37,9 +37,6 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( :: Tests in the extension host -REM call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-notebook-tests\test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-notebook-tests --extensionTestsPath=%~dp0\..\extensions\vscode-notebook-tests\out --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% -REM if %errorlevel% neq 0 exit /b %errorlevel% - REM call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\singlefolder-tests --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% REM if %errorlevel% neq 0 exit /b %errorlevel% @@ -61,6 +58,9 @@ REM if %errorlevel% neq 0 exit /b %errorlevel% call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\azurecore\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\azurecore --extensionTestsPath=%~dp0\..\extensions\azurecore\out\test --no-cached-data --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% if %errorlevel% neq 0 exit /b %errorlevel% +REM call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-notebook-tests\test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-notebook-tests --extensionTestsPath=%~dp0\..\extensions\vscode-notebook-tests\out --disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% +REM if %errorlevel% neq 0 exit /b %errorlevel% + for /f "delims=" %%i in ('node -p "require('fs').realpathSync.native(require('os').tmpdir())"') do set TEMPDIR=%%i set GITWORKSPACE=%TEMPDIR%\git-%RANDOM% mkdir %GITWORKSPACE% diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index d1932f47e8..d188ed7dff 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -48,7 +48,7 @@ fi # "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/typescript-language-features/test-workspace --extensionDevelopmentPath=$ROOT/extensions/typescript-language-features --extensionTestsPath=$ROOT/extensions/typescript-language-features/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR # "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/emmet/out/test/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $(mktemp -d 2>/dev/null) --enable-proposed-api=vscode.git --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR -# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-notebook-tests/test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-notebook-tests --extensionTestsPath=$ROOT/extensions/vscode-notebook-tests/out/ --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-notebook-tests/test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-notebook-tests --extensionTestsPath=$ROOT/extensions/vscode-notebook-tests/out/ --disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/azurecore/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/azurecore --extensionTestsPath=$ROOT/extensions/azurecore/out/test --no-cached-data --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR diff --git a/src/vs/base/browser/contextmenu.ts b/src/vs/base/browser/contextmenu.ts index 5e61d17445..5cd028f8c5 100644 --- a/src/vs/base/browser/contextmenu.ts +++ b/src/vs/base/browser/contextmenu.ts @@ -34,5 +34,5 @@ export interface IContextMenuDelegate { actionRunner?: IActionRunner; autoSelectFirstItem?: boolean; anchorAlignment?: AnchorAlignment; - anchorAsContainer?: boolean; + domForShadowRoot?: HTMLElement; } diff --git a/src/vs/base/browser/ui/actionbar/actionbar.css b/src/vs/base/browser/ui/actionbar/actionbar.css index da795d4656..04fa9dc01e 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.css +++ b/src/vs/base/browser/ui/actionbar/actionbar.css @@ -5,7 +5,6 @@ .monaco-action-bar { text-align: right; - overflow: hidden; white-space: nowrap; } diff --git a/src/vs/base/browser/ui/codicons/codiconStyles.ts b/src/vs/base/browser/ui/codicons/codiconStyles.ts index 1fdeaa6f07..274e9de74c 100644 --- a/src/vs/base/browser/ui/codicons/codiconStyles.ts +++ b/src/vs/base/browser/ui/codicons/codiconStyles.ts @@ -28,7 +28,7 @@ function initialize() { delayer.schedule(); } -function formatRule(c: Codicon) { +export function formatRule(c: Codicon) { let def = c.definition; while (def instanceof Codicon) { def = def.definition; diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index b09b736630..f6b1bae867 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -10,6 +10,12 @@ import { IDisposable, toDisposable, Disposable, DisposableStore } from 'vs/base/ import { Range } from 'vs/base/common/range'; import { BrowserFeatures } from 'vs/base/browser/canIUse'; +export const enum ContextViewDOMPosition { + ABSOLUTE = 1, + FIXED, + FIXED_SHADOW +} + export interface IAnchor { x: number; y: number; @@ -105,32 +111,62 @@ export class ContextView extends Disposable { private container: HTMLElement | null = null; private view: HTMLElement; private useFixedPosition: boolean; + private useShadowDOM: boolean; private delegate: IDelegate | null = null; private toDisposeOnClean: IDisposable = Disposable.None; private toDisposeOnSetContainer: IDisposable = Disposable.None; + private shadowRoot: ShadowRoot | null = null; + private shadowRootHostElement: HTMLElement | null = null; - constructor(container: HTMLElement, useFixedPosition: boolean) { + constructor(container: HTMLElement, domPosition: ContextViewDOMPosition) { super(); this.view = DOM.$('.context-view'); this.useFixedPosition = false; + this.useShadowDOM = false; DOM.hide(this.view); - this.setContainer(container, useFixedPosition); + this.setContainer(container, domPosition); - this._register(toDisposable(() => this.setContainer(null, false))); + this._register(toDisposable(() => this.setContainer(null, ContextViewDOMPosition.ABSOLUTE))); } - setContainer(container: HTMLElement | null, useFixedPosition: boolean): void { + setContainer(container: HTMLElement | null, domPosition: ContextViewDOMPosition): void { if (this.container) { this.toDisposeOnSetContainer.dispose(); - this.container.removeChild(this.view); + + if (this.shadowRoot) { + this.shadowRoot.removeChild(this.view); + this.shadowRoot = null; + DOM.removeNode(this.shadowRootHostElement!); + this.shadowRootHostElement = null; + } else { + this.container.removeChild(this.view); + } + this.container = null; } if (container) { this.container = container; - this.container.appendChild(this.view); + + this.useFixedPosition = domPosition !== ContextViewDOMPosition.ABSOLUTE; + this.useShadowDOM = domPosition === ContextViewDOMPosition.FIXED_SHADOW; + + if (this.useShadowDOM) { + this.shadowRootHostElement = DOM.$('.shadow-root-host'); + this.container.appendChild(this.shadowRootHostElement); + this.shadowRoot = this.shadowRootHostElement.attachShadow({ mode: 'closed' }); + this.shadowRoot.innerHTML = ` + + `; + this.shadowRoot.appendChild(this.view); + this.shadowRoot.appendChild(DOM.$('slot')); + } else { + this.container.appendChild(this.view); + } const toDisposeOnSetContainer = new DisposableStore(); @@ -148,8 +184,6 @@ export class ContextView extends Disposable { this.toDisposeOnSetContainer = toDisposeOnSetContainer; } - - this.useFixedPosition = useFixedPosition; } show(delegate: IDelegate): void { @@ -162,6 +196,7 @@ export class ContextView extends Disposable { this.view.className = 'context-view'; this.view.style.top = '0px'; this.view.style.left = '0px'; + this.view.style.zIndex = '2500'; this.view.style.position = this.useFixedPosition ? 'fixed' : 'absolute'; DOM.show(this.view); @@ -301,3 +336,45 @@ export class ContextView extends Disposable { super.dispose(); } } + +let SHADOW_ROOT_CSS = /* css */ ` + :host { + all: initial; /* 1st rule so subsequent properties are reset. */ + } + + @font-face { + font-family: "codicon"; + src: url("./codicon.ttf?5d4d76ab2ce5108968ad644d591a16a6") format("truetype"); + } + + .codicon[class*='codicon-'] { + font: normal normal normal 16px/1 codicon; + display: inline-block; + text-decoration: none; + text-rendering: auto; + text-align: center; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + } + + :host-context(.mac) { font-family: -apple-system, BlinkMacSystemFont, sans-serif; } + :host-context(.mac:lang(zh-Hans)) { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; } + :host-context(.mac:lang(zh-Hant)) { font-family: -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; } + :host-context(.mac:lang(ja)) { font-family: -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic Pro", sans-serif; } + :host-context(.mac:lang(ko)) { font-family: -apple-system, BlinkMacSystemFont, "Nanum Gothic", "Apple SD Gothic Neo", "AppleGothic", sans-serif; } + + :host-context(.windows) { font-family: "Segoe WPC", "Segoe UI", sans-serif; } + :host-context(.windows:lang(zh-Hans)) { font-family: "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; } + :host-context(.windows:lang(zh-Hant)) { font-family: "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; } + :host-context(.windows:lang(ja)) { font-family: "Segoe WPC", "Segoe UI", "Yu Gothic UI", "Meiryo UI", sans-serif; } + :host-context(.windows:lang(ko)) { font-family: "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; } + + :host-context(.mac).linux) { font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; } + :host-context(.mac).linux:lang(zh-Hans)) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; } + :host-context(.mac).linux:lang(zh-Hant)) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; } + :host-context(.mac).linux:lang(ja)) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; } + :host-context(.mac).linux:lang(ko)) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; } +`; diff --git a/src/vs/base/browser/ui/dropdown/dropdown.ts b/src/vs/base/browser/ui/dropdown/dropdown.ts index 725ce0a706..b9c33e6506 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -270,7 +270,7 @@ export class DropdownMenu extends BaseDropdown { onHide: () => this.onHide(), actionRunner: this.menuOptions ? this.menuOptions.actionRunner : undefined, anchorAlignment: this.menuOptions ? this.menuOptions.anchorAlignment : AnchorAlignment.LEFT, - anchorAsContainer: this.menuAsChild + domForShadowRoot: this.menuAsChild ? this.element : undefined }); } @@ -297,15 +297,17 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { private _onDidChangeVisibility = this._register(new Emitter()); readonly onDidChangeVisibility = this._onDidChangeVisibility.event; - constructor(action: IAction, menuActions: ReadonlyArray, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean); - constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean); - constructor(action: IAction, menuActionsOrProvider: ReadonlyArray | IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean) { + constructor(action: IAction, menuActions: ReadonlyArray, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner | undefined, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean); + constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner | undefined, keybindings: ((action: IAction) => ResolvedKeybinding) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean); + constructor(action: IAction, menuActionsOrProvider: ReadonlyArray | IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner | undefined, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean) { super(null, action); this.menuActionsOrProvider = menuActionsOrProvider; this.contextMenuProvider = contextMenuProvider; this.actionViewItemProvider = actionViewItemProvider; - this.actionRunner = actionRunner; + if (actionRunner) { + this.actionRunner = actionRunner; + } this.keybindings = keybindings; this.clazz = clazz; this.anchorAlignmentProvider = anchorAlignmentProvider; diff --git a/src/vs/base/browser/ui/menu/menu.css b/src/vs/base/browser/ui/menu/menu.css deleted file mode 100644 index de8e55b969..0000000000 --- a/src/vs/base/browser/ui/menu/menu.css +++ /dev/null @@ -1,225 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-menu .monaco-action-bar.vertical { - margin-left: 0; - overflow: visible; -} - -.monaco-menu .monaco-action-bar.vertical .actions-container { - display: block; -} - -.monaco-menu .monaco-action-bar.vertical .action-item { - padding: 0; - transform: none; - display: flex; -} - -.monaco-menu .monaco-action-bar.vertical .action-item.active { - transform: none; -} - -.monaco-menu .monaco-action-bar.vertical .action-menu-item { - flex: 1 1 auto; - display: flex; - height: 2em; - align-items: center; - position: relative; -} - -.monaco-menu .monaco-action-bar.vertical .action-label { - flex: 1 1 auto; - text-decoration: none; - padding: 0 1em; - background: none; - font-size: 12px; - line-height: 1; -} - -.monaco-menu .monaco-action-bar.vertical .keybinding, -.monaco-menu .monaco-action-bar.vertical .submenu-indicator { - display: inline-block; - flex: 2 1 auto; - padding: 0 1em; - text-align: right; - font-size: 12px; - line-height: 1; -} - -.monaco-menu .monaco-action-bar.vertical .submenu-indicator { - height: 100%; -} - -.monaco-menu .monaco-action-bar.vertical .submenu-indicator.codicon { - font-size: 16px !important; - display: flex; - align-items: center; -} - -.monaco-menu .monaco-action-bar.vertical .submenu-indicator.codicon::before { - margin-left: auto; - margin-right: -20px; -} - -.monaco-menu .monaco-action-bar.vertical .action-item.disabled .keybinding, -.monaco-menu .monaco-action-bar.vertical .action-item.disabled .submenu-indicator { - opacity: 0.4; -} - -.monaco-menu .monaco-action-bar.vertical .action-label:not(.separator) { - display: inline-block; - box-sizing: border-box; - margin: 0; -} - -.monaco-menu .monaco-action-bar.vertical .action-item { - position: static; - overflow: visible; -} - -.monaco-menu .monaco-action-bar.vertical .action-item .monaco-submenu { - position: absolute; -} - -.monaco-menu .monaco-action-bar.vertical .action-label.separator { - padding: 0.5em 0 0 0; - margin-bottom: 0.5em; - width: 100%; - height: 0px !important; - margin-left: .8em !important; - margin-right: .8em !important; -} - -.monaco-menu .monaco-action-bar.vertical .action-label.separator.text { - padding: 0.7em 1em 0.1em 1em; - font-weight: bold; - opacity: 1; -} - -.monaco-menu .monaco-action-bar.vertical .action-label:hover { - color: inherit; -} - -.monaco-menu .monaco-action-bar.vertical .menu-item-check { - position: absolute; - visibility: hidden; - width: 1em; - height: 100%; -} - -.monaco-menu .monaco-action-bar.vertical .action-menu-item.checked .menu-item-check { - visibility: visible; - display: flex; - align-items: center; - justify-content: center; -} - -/* Context Menu */ - -.context-view.monaco-menu-container { - outline: 0; - border: none; - animation: fadeIn 0.083s linear; -} - -.context-view.monaco-menu-container :focus, -.context-view.monaco-menu-container .monaco-action-bar.vertical:focus, -.context-view.monaco-menu-container .monaco-action-bar.vertical :focus { - outline: 0; -} - -.monaco-menu .monaco-action-bar.vertical .action-item { - border: thin solid transparent; /* prevents jumping behaviour on hover or focus */ -} - - -/* High Contrast Theming */ -.hc-black .context-view.monaco-menu-container { - box-shadow: none; -} - -.hc-black .monaco-menu .monaco-action-bar.vertical .action-item.focused { - background: none; -} - -/* Menubar styles */ - -.menubar { - display: flex; - flex-shrink: 1; - box-sizing: border-box; - height: 30px; - overflow: hidden; - flex-wrap: wrap; -} - -.fullscreen .menubar:not(.compact) { - margin: 0px; - padding: 0px 5px; -} - -.menubar > .menubar-menu-button { - align-items: center; - box-sizing: border-box; - padding: 0px 8px; - cursor: default; - -webkit-app-region: no-drag; - zoom: 1; - white-space: nowrap; - outline: 0; -} - -.menubar.compact { - flex-shrink: 0; - overflow: visible; /* to avoid the compact menu to be repositioned when clicking */ -} - -.menubar.compact > .menubar-menu-button { - width: 100%; - height: 100%; - padding: 0px; -} - -.menubar .menubar-menu-items-holder { - position: absolute; - left: 0px; - opacity: 1; - z-index: 2000; -} - -.menubar .menubar-menu-items-holder.monaco-menu-container { - outline: 0; - border: none; -} - -.menubar .menubar-menu-items-holder.monaco-menu-container :focus { - outline: 0; -} - -.menubar .toolbar-toggle-more { - width: 20px; - height: 100%; -} - -.menubar.compact .toolbar-toggle-more { - position: relative; - left: 0px; - top: 0px; - cursor: pointer; - width: 100%; - display: flex; - align-items: center; - justify-content: center; -} - -.menubar .toolbar-toggle-more { - padding: 0; - vertical-align: sub; -} - -.menubar.compact .toolbar-toggle-more::before { - content: "\eb94" !important; -} diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index aa54df4c43..4035da836c 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -3,13 +3,12 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./menu'; import * as nls from 'vs/nls'; import * as strings from 'vs/base/common/strings'; import { IActionRunner, IAction, Action } from 'vs/base/common/actions'; import { ActionBar, IActionViewItemProvider, ActionsOrientation, Separator, ActionViewItem, IActionViewItemOptions, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes'; -import { addClass, EventType, EventHelper, EventLike, removeTabIndexAndUpdateFocus, isAncestor, hasClass, addDisposableListener, removeClass, append, $, addClasses, removeClasses, clearNode } from 'vs/base/browser/dom'; +import { addClass, EventType, EventHelper, EventLike, removeTabIndexAndUpdateFocus, isAncestor, hasClass, addDisposableListener, removeClass, append, $, addClasses, removeClasses, clearNode, createStyleSheet, isInShadowDOM } from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { RunOnceScheduler } from 'vs/base/common/async'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -20,6 +19,7 @@ import { Event } from 'vs/base/common/event'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { isLinux, isMacintosh } from 'vs/base/common/platform'; import { Codicon, registerIcon, stripCodicons } from 'vs/base/common/codicons'; +import { formatRule } from 'vs/base/browser/ui/codicons/codiconStyles'; export const MENU_MNEMONIC_REGEX = /\(&([^\s&])\)|(^|[^&])&([^\s&])/; export const MENU_ESCAPED_MNEMONIC_REGEX = /(&)?(&)([^\s&])/g; @@ -71,6 +71,8 @@ export class Menu extends ActionBar { private readonly menuDisposables: DisposableStore; private scrollableElement: DomScrollableElement; private menuElement: HTMLElement; + static globalStyleSheet: HTMLStyleElement; + protected styleSheet: HTMLStyleElement | undefined; constructor(container: HTMLElement, actions: ReadonlyArray, options: IMenuOptions = {}) { addClass(container, 'monaco-menu-container'); @@ -96,6 +98,8 @@ export class Menu extends ActionBar { this.menuDisposables = this._register(new DisposableStore()); + this.initializeStyleSheet(container); + addDisposableListener(menuElement, EventType.KEY_DOWN, (e) => { const event = new StandardKeyboardEvent(e); @@ -215,6 +219,20 @@ export class Menu extends ActionBar { }); } + private initializeStyleSheet(container: HTMLElement): void { + if (isInShadowDOM(container)) { + this.styleSheet = createStyleSheet(container); + this.styleSheet.innerHTML = MENU_WIDGET_CSS; + } else { + if (!Menu.globalStyleSheet) { + Menu.globalStyleSheet = createStyleSheet(); + Menu.globalStyleSheet.innerHTML = MENU_WIDGET_CSS; + } + + this.styleSheet = Menu.globalStyleSheet; + } + } + style(style: IMenuStyles): void { const container = this.getContainer(); @@ -877,3 +895,298 @@ export function cleanMnemonic(label: string): string { return label.replace(regex, mnemonicInText ? '$2$3' : '').trim(); } + +let MENU_WIDGET_CSS: string = /* css */` +.monaco-menu { + font-size: 13px; + +} + +${formatRule(menuSelectionIcon)} +${formatRule(menuSubmenuIcon)} + +.monaco-action-bar { + text-align: right; + overflow: hidden; + white-space: nowrap; +} + +.monaco-action-bar .actions-container { + display: flex; + margin: 0 auto; + padding: 0; + width: 100%; + justify-content: flex-end; +} + +.monaco-action-bar.vertical .actions-container { + display: inline-block; +} + +.monaco-action-bar.reverse .actions-container { + flex-direction: row-reverse; +} + +.monaco-action-bar .action-item { + cursor: pointer; + display: inline-block; + transition: transform 50ms ease; + position: relative; /* DO NOT REMOVE - this is the key to preventing the ghosting icon bug in Chrome 42 */ +} + +.monaco-action-bar .action-item.disabled { + cursor: default; +} + +.monaco-action-bar.animated .action-item.active { + transform: scale(1.272019649, 1.272019649); /* 1.272019649 = √φ */ +} + +.monaco-action-bar .action-item .icon, +.monaco-action-bar .action-item .codicon { + display: inline-block; +} + +.monaco-action-bar .action-item .codicon { + display: flex; + align-items: center; +} + +.monaco-action-bar .action-label { + font-size: 11px; + margin-right: 4px; +} + +.monaco-action-bar .action-item.disabled .action-label, +.monaco-action-bar .action-item.disabled .action-label:hover { + opacity: 0.4; +} + +/* Vertical actions */ + +.monaco-action-bar.vertical { + text-align: left; +} + +.monaco-action-bar.vertical .action-item { + display: block; +} + +.monaco-action-bar.vertical .action-label.separator { + display: block; + border-bottom: 1px solid #bbb; + padding-top: 1px; + margin-left: .8em; + margin-right: .8em; +} + +.monaco-action-bar.animated.vertical .action-item.active { + transform: translate(5px, 0); +} + +.secondary-actions .monaco-action-bar .action-label { + margin-left: 6px; +} + +/* Action Items */ +.monaco-action-bar .action-item.select-container { + overflow: hidden; /* somehow the dropdown overflows its container, we prevent it here to not push */ + flex: 1; + max-width: 170px; + min-width: 60px; + display: flex; + align-items: center; + justify-content: center; + margin-right: 10px; +} + +.monaco-menu .monaco-action-bar.vertical { + margin-left: 0; + overflow: visible; +} + +.monaco-menu .monaco-action-bar.vertical .actions-container { + display: block; +} + +.monaco-menu .monaco-action-bar.vertical .action-item { + padding: 0; + transform: none; + display: flex; +} + +.monaco-menu .monaco-action-bar.vertical .action-item.active { + transform: none; +} + +.monaco-menu .monaco-action-bar.vertical .action-menu-item { + flex: 1 1 auto; + display: flex; + height: 2em; + align-items: center; + position: relative; +} + +.monaco-menu .monaco-action-bar.vertical .action-label { + flex: 1 1 auto; + text-decoration: none; + padding: 0 1em; + background: none; + font-size: 12px; + line-height: 1; +} + +.monaco-menu .monaco-action-bar.vertical .keybinding, +.monaco-menu .monaco-action-bar.vertical .submenu-indicator { + display: inline-block; + flex: 2 1 auto; + padding: 0 1em; + text-align: right; + font-size: 12px; + line-height: 1; +} + +.monaco-menu .monaco-action-bar.vertical .submenu-indicator { + height: 100%; +} + +.monaco-menu .monaco-action-bar.vertical .submenu-indicator.codicon { + font-size: 16px !important; + display: flex; + align-items: center; +} + +.monaco-menu .monaco-action-bar.vertical .submenu-indicator.codicon::before { + margin-left: auto; + margin-right: -20px; +} + +.monaco-menu .monaco-action-bar.vertical .action-item.disabled .keybinding, +.monaco-menu .monaco-action-bar.vertical .action-item.disabled .submenu-indicator { + opacity: 0.4; +} + +.monaco-menu .monaco-action-bar.vertical .action-label:not(.separator) { + display: inline-block; + box-sizing: border-box; + margin: 0; +} + +.monaco-menu .monaco-action-bar.vertical .action-item { + position: static; + overflow: visible; +} + +.monaco-menu .monaco-action-bar.vertical .action-item .monaco-submenu { + position: absolute; +} + +.monaco-menu .monaco-action-bar.vertical .action-label.separator { + padding: 0.5em 0 0 0; + margin-bottom: 0.5em; + width: 100%; + height: 0px !important; + margin-left: .8em !important; + margin-right: .8em !important; +} + +.monaco-menu .monaco-action-bar.vertical .action-label.separator.text { + padding: 0.7em 1em 0.1em 1em; + font-weight: bold; + opacity: 1; +} + +.monaco-menu .monaco-action-bar.vertical .action-label:hover { + color: inherit; +} + +.monaco-menu .monaco-action-bar.vertical .menu-item-check { + position: absolute; + visibility: hidden; + width: 1em; + height: 100%; +} + +.monaco-menu .monaco-action-bar.vertical .action-menu-item.checked .menu-item-check { + visibility: visible; + display: flex; + align-items: center; + justify-content: center; +} + +/* Context Menu */ + +.context-view.monaco-menu-container { + outline: 0; + border: none; + animation: fadeIn 0.083s linear; +} + +.context-view.monaco-menu-container :focus, +.context-view.monaco-menu-container .monaco-action-bar.vertical:focus, +.context-view.monaco-menu-container .monaco-action-bar.vertical :focus { + outline: 0; +} + +.monaco-menu .monaco-action-bar.vertical .action-item { + border: thin solid transparent; /* prevents jumping behaviour on hover or focus */ +} + + +/* High Contrast Theming */ +.hc-black .context-view.monaco-menu-container { + box-shadow: none; +} + +.hc-black .monaco-menu .monaco-action-bar.vertical .action-item.focused { + background: none; +} + +/* Vertical Action Bar Styles */ + +.monaco-menu .monaco-action-bar.vertical { + padding: .5em 0; +} + +.monaco-menu .monaco-action-bar.vertical .action-menu-item { + height: 1.8em; +} + +.monaco-menu .monaco-action-bar.vertical .action-label:not(.separator), +.monaco-menu .monaco-action-bar.vertical .keybinding { + font-size: inherit; + padding: 0 2em; +} + +.monaco-menu .monaco-action-bar.vertical .menu-item-check { + font-size: inherit; + width: 2em; +} + +.monaco-menu .monaco-action-bar.vertical .action-label.separator { + font-size: inherit; + padding: 0.2em 0 0 0; + margin-bottom: 0.2em; +} + +linux .monaco-menu .monaco-action-bar.vertical .action-label.separator { + margin-left: 0; + margin-right: 0; +} + +.monaco-menu .monaco-action-bar.vertical .submenu-indicator { + font-size: 60%; + padding: 0 1.8em; +} + +:host-context(.linux) .monaco-menu .monaco-action-bar.vertical .submenu-indicator { + height: 100%; + mask-size: 10px 10px; + -webkit-mask-size: 10px 10px; +} + +.monaco-menu .action-item { + cursor: default; +} + +`; diff --git a/src/vs/base/browser/ui/menu/menubar.css b/src/vs/base/browser/ui/menu/menubar.css new file mode 100644 index 0000000000..abd299fa01 --- /dev/null +++ b/src/vs/base/browser/ui/menu/menubar.css @@ -0,0 +1,83 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/* Menubar styles */ + +.menubar { + display: flex; + flex-shrink: 1; + box-sizing: border-box; + height: 30px; + overflow: hidden; + flex-wrap: wrap; +} + +.fullscreen .menubar:not(.compact) { + margin: 0px; + padding: 0px 5px; +} + +.menubar > .menubar-menu-button { + align-items: center; + box-sizing: border-box; + padding: 0px 8px; + cursor: default; + -webkit-app-region: no-drag; + zoom: 1; + white-space: nowrap; + outline: 0; +} + +.menubar.compact { + flex-shrink: 0; + overflow: visible; /* to avoid the compact menu to be repositioned when clicking */ +} + +.menubar.compact > .menubar-menu-button { + width: 100%; + height: 100%; + padding: 0px; +} + +.menubar .menubar-menu-items-holder { + position: absolute; + left: 0px; + opacity: 1; + z-index: 2000; +} + +.menubar .menubar-menu-items-holder.monaco-menu-container { + outline: 0; + border: none; +} + +.menubar .menubar-menu-items-holder.monaco-menu-container :focus { + outline: 0; +} + +.menubar .toolbar-toggle-more { + width: 20px; + height: 100%; +} + +.menubar.compact .toolbar-toggle-more { + position: relative; + left: 0px; + top: 0px; + cursor: pointer; + width: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +.menubar .toolbar-toggle-more { + padding: 0; + vertical-align: sub; +} + +.menubar.compact .toolbar-toggle-more::before { + content: "\eb94" !important; +} diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index bf14a48e8c..7fa6a5984f 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./menubar'; import * as browser from 'vs/base/browser/browser'; import * as DOM from 'vs/base/browser/dom'; import * as strings from 'vs/base/common/strings'; diff --git a/src/vs/base/common/marshalling.ts b/src/vs/base/common/marshalling.ts index d0c93662e2..d5267bff98 100644 --- a/src/vs/base/common/marshalling.ts +++ b/src/vs/base/common/marshalling.ts @@ -3,8 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI } from 'vs/base/common/uri'; +import { VSBuffer } from 'vs/base/common/buffer'; import { regExpFlags } from 'vs/base/common/strings'; +import { URI } from 'vs/base/common/uri'; export function stringify(obj: any): string { return JSON.stringify(obj, replacer); @@ -44,10 +45,23 @@ export function revive(obj: any, depth = 0): any { case 2: return new RegExp(obj.source, obj.flags); } - // walk object (or array) - for (let key in obj) { - if (Object.hasOwnProperty.call(obj, key)) { - obj[key] = revive(obj[key], depth + 1); + if ( + obj instanceof VSBuffer + || obj instanceof Uint8Array + ) { + return obj; + } + + if (Array.isArray(obj)) { + for (let i = 0; i < obj.length; ++i) { + obj[i] = revive(obj[i], depth + 1); + } + } else { + // walk object + for (const key in obj) { + if (Object.hasOwnProperty.call(obj, key)) { + obj[key] = revive(obj[key], depth + 1); + } } } } diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts index effadd30fb..93dde1871c 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -16,6 +16,7 @@ export interface ISocket extends IDisposable { onEnd(listener: () => void): IDisposable; write(buffer: VSBuffer): void; end(): void; + drain(): Promise; } let emptyBuffer: VSBuffer | null = null; @@ -277,6 +278,11 @@ class ProtocolWriter { this._isDisposed = true; } + public drain(): Promise { + this.flush(); + return this._socket.drain(); + } + public flush(): void { // flush this._writeNow(); @@ -372,6 +378,10 @@ export class Protocol extends Disposable implements IMessagePassingProtocol { this._register(this._socket.onClose(() => this._onClose.fire())); } + drain(): Promise { + return this._socketWriter.drain(); + } + getSocket(): ISocket { return this._socket; } @@ -619,6 +629,10 @@ export class PersistentProtocol implements IMessagePassingProtocol { this._socketDisposables = dispose(this._socketDisposables); } + drain(): Promise { + return this._socketWriter.drain(); + } + sendDisconnect(): void { const msg = new ProtocolMessage(ProtocolMessageType.Disconnect, 0, 0, getEmptyBuffer()); this._socketWriter.write(msg); diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index 5a37a46ca9..8f21ebee69 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -70,6 +70,10 @@ interface IHandler { export interface IMessagePassingProtocol { send(buffer: VSBuffer): void; onMessage: Event; + /** + * Wait for the write buffer (if applicable) to become empty. + */ + drain?(): Promise; } enum State { @@ -482,10 +486,7 @@ export class ChannelClient implements IChannelClient, IDisposable { return e(errors.canceled()); } - let uninitializedPromise: CancelablePromise | null = createCancelablePromise(_ => this.whenInitialized()); - uninitializedPromise.then(() => { - uninitializedPromise = null; - + const doRequest = () => { const handler: IHandler = response => { switch (response.type) { case ResponseType.PromiseSuccess: @@ -510,7 +511,18 @@ export class ChannelClient implements IChannelClient, IDisposable { this.handlers.set(id, handler); this.sendRequest(request); - }); + }; + + let uninitializedPromise: CancelablePromise | null = null; + if (this.state === State.Idle) { + doRequest(); + } else { + uninitializedPromise = createCancelablePromise(_ => this.whenInitialized()); + uninitializedPromise.then(() => { + uninitializedPromise = null; + doRequest(); + }); + } const cancel = () => { if (uninitializedPromise) { diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index 1a96d81731..c9f0140ab6 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -12,6 +12,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { VSBuffer } from 'vs/base/common/buffer'; import { ISocket, Protocol, Client, ChunkStream } from 'vs/base/parts/ipc/common/ipc.net'; +import { onUnexpectedError } from 'vs/base/common/errors'; export class NodeSocket implements ISocket { public readonly socket: Socket; @@ -57,12 +58,47 @@ export class NodeSocket implements ISocket { // > https://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback // > However, the false return value is only advisory and the writable stream will unconditionally // > accept and buffer chunk even if it has not been allowed to drain. - this.socket.write(buffer.buffer); + try { + this.socket.write(buffer.buffer); + } catch (err) { + if (err.code === 'EPIPE') { + // An EPIPE exception at the wrong time can lead to a renderer process crash + // so ignore the error since the socket will fire the close event soon anyways: + // > https://nodejs.org/api/errors.html#errors_common_system_errors + // > EPIPE (Broken pipe): A write on a pipe, socket, or FIFO for which there is no + // > process to read the data. Commonly encountered at the net and http layers, + // > indicative that the remote side of the stream being written to has been closed. + return; + } + onUnexpectedError(err); + } } public end(): void { this.socket.end(); } + + public drain(): Promise { + return new Promise((resolve, reject) => { + if (this.socket.bufferSize === 0) { + resolve(); + return; + } + const finished = () => { + this.socket.off('close', finished); + this.socket.off('end', finished); + this.socket.off('error', finished); + this.socket.off('timeout', finished); + this.socket.off('drain', finished); + resolve(); + }; + this.socket.on('close', finished); + this.socket.on('end', finished); + this.socket.on('error', finished); + this.socket.on('timeout', finished); + this.socket.on('drain', finished); + }); + } } const enum Constants { @@ -229,6 +265,10 @@ export class WebSocketNodeSocket extends Disposable implements ISocket { } } } + + public drain(): Promise { + return this.socket.drain(); + } } function unmask(buffer: VSBuffer, mask: number): void { diff --git a/src/vs/editor/contrib/codeAction/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/codeActionMenu.ts index a073034b41..9eeb38d33a 100644 --- a/src/vs/editor/contrib/codeAction/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/codeActionMenu.ts @@ -90,6 +90,7 @@ export class CodeActionMenu extends Disposable { const resolver = this._keybindingResolver.getResolver(); this._contextMenuService.showContextMenu({ + domForShadowRoot: this._editor.getDomNode()!, getAnchor: () => anchor, getActions: () => menuActions, onHide: () => { diff --git a/src/vs/editor/contrib/contextmenu/contextmenu.ts b/src/vs/editor/contrib/contextmenu/contextmenu.ts index cc0334ed97..dda6683316 100644 --- a/src/vs/editor/contrib/contextmenu/contextmenu.ts +++ b/src/vs/editor/contrib/contextmenu/contextmenu.ts @@ -205,6 +205,8 @@ export class ContextMenuController implements IEditorContribution { // Show menu this._contextMenuIsBeingShownCount++; this._contextMenuService.showContextMenu({ + domForShadowRoot: this._editor.getDomNode(), + getAnchor: () => anchor!, getActions: () => actions, diff --git a/src/vs/editor/contrib/dnd/dnd.ts b/src/vs/editor/contrib/dnd/dnd.ts index f7fc0f3f26..e0b6e69106 100644 --- a/src/vs/editor/contrib/dnd/dnd.ts +++ b/src/vs/editor/contrib/dnd/dnd.ts @@ -54,6 +54,7 @@ export class DragAndDropController extends Disposable implements IEditorContribu this._register(this._editor.onKeyDown((e: IKeyboardEvent) => this.onEditorKeyDown(e))); this._register(this._editor.onKeyUp((e: IKeyboardEvent) => this.onEditorKeyUp(e))); this._register(this._editor.onDidBlurEditorWidget(() => this.onEditorBlur())); + this._register(this._editor.onDidBlurEditorText(() => this.onEditorBlur())); this._dndDecorationIds = []; this._mouseDown = false; this._modifierPressed = false; diff --git a/src/vs/editor/contrib/find/findWidget.css b/src/vs/editor/contrib/find/findWidget.css index 35aef59279..adfec7b508 100644 --- a/src/vs/editor/contrib/find/findWidget.css +++ b/src/vs/editor/contrib/find/findWidget.css @@ -6,7 +6,7 @@ /* Find widget */ .monaco-editor .find-widget { position: absolute; - z-index: 10; + z-index: 20; height: 33px; overflow: hidden; line-height: 19px; diff --git a/src/vs/platform/contextview/browser/contextMenuHandler.ts b/src/vs/platform/contextview/browser/contextMenuHandler.ts index d2b63d5f03..ce5b484ce7 100644 --- a/src/vs/platform/contextview/browser/contextMenuHandler.ts +++ b/src/vs/platform/contextview/browser/contextMenuHandler.ts @@ -50,7 +50,7 @@ export class ContextMenuHandler { let menu: Menu | undefined; - const anchor = delegate.getAnchor(); + let shadowRootElement = isHTMLElement(delegate.domForShadowRoot) ? delegate.domForShadowRoot : undefined; this.contextViewService.showContextView({ getAnchor: () => delegate.getAnchor(), canRelayout: false, @@ -133,7 +133,7 @@ export class ContextMenuHandler { this.focusToReturn.focus(); } } - }, !!delegate.anchorAsContainer && isHTMLElement(anchor) ? anchor : undefined); + }, shadowRootElement, !!shadowRootElement); } private onActionRun(e: IRunEvent): void { diff --git a/src/vs/platform/contextview/browser/contextView.ts b/src/vs/platform/contextview/browser/contextView.ts index 328916929b..1e907d13b9 100644 --- a/src/vs/platform/contextview/browser/contextView.ts +++ b/src/vs/platform/contextview/browser/contextView.ts @@ -15,7 +15,7 @@ export interface IContextViewService extends IContextViewProvider { readonly _serviceBrand: undefined; - showContextView(delegate: IContextViewDelegate, container?: HTMLElement): IDisposable; + showContextView(delegate: IContextViewDelegate, container?: HTMLElement, shadowRoot?: boolean): IDisposable; hideContextView(data?: any): void; layout(): void; anchorAlignment?: AnchorAlignment; diff --git a/src/vs/platform/contextview/browser/contextViewService.ts b/src/vs/platform/contextview/browser/contextViewService.ts index 1875694c0b..6ee3353055 100644 --- a/src/vs/platform/contextview/browser/contextViewService.ts +++ b/src/vs/platform/contextview/browser/contextViewService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IContextViewService, IContextViewDelegate } from './contextView'; -import { ContextView } from 'vs/base/browser/ui/contextview/contextview'; +import { ContextView, ContextViewDOMPosition } from 'vs/base/browser/ui/contextview/contextview'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; @@ -21,7 +21,7 @@ export class ContextViewService extends Disposable implements IContextViewServic super(); this.container = layoutService.container; - this.contextView = this._register(new ContextView(this.container, false)); + this.contextView = this._register(new ContextView(this.container, ContextViewDOMPosition.ABSOLUTE)); this.layout(); this._register(layoutService.onLayout(() => this.layout())); @@ -29,20 +29,20 @@ export class ContextViewService extends Disposable implements IContextViewServic // ContextView - setContainer(container: HTMLElement, useFixedPosition?: boolean): void { - this.contextView.setContainer(container, !!useFixedPosition); + setContainer(container: HTMLElement, domPosition?: ContextViewDOMPosition): void { + this.contextView.setContainer(container, domPosition || ContextViewDOMPosition.ABSOLUTE); } - showContextView(delegate: IContextViewDelegate, container?: HTMLElement): IDisposable { + showContextView(delegate: IContextViewDelegate, container?: HTMLElement, shadowRoot?: boolean): IDisposable { if (container) { if (container !== this.container) { this.container = container; - this.setContainer(container, true); + this.setContainer(container, shadowRoot ? ContextViewDOMPosition.FIXED_SHADOW : ContextViewDOMPosition.FIXED); } } else { if (this.container !== this.layoutService.container) { this.container = this.layoutService.container; - this.setContainer(this.container, false); + this.setContainer(this.container, ContextViewDOMPosition.ABSOLUTE); } } diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index b18ed3c7fd..28ca5155ae 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -149,8 +149,25 @@ export interface IExtensionIdentifier { uuid?: string; } -export const EXTENSION_CATEGORIES = ['Programming Languages', 'Snippets', 'Linters', 'Themes', 'Debuggers', 'Other', 'Keymaps', 'Formatters', 'Extension Packs', - 'SCM Providers', 'Azure', 'Language Packs', 'Data Science', 'Machine Learning', 'Visualization', 'Testing', 'Notebooks']; +export const EXTENSION_CATEGORIES = [ + 'Azure', + 'Data Science', + 'Debuggers', + 'Extension Packs', + 'Formatters', + 'Keymaps', + 'Language Packs', + 'Linters', + 'Machine Learning', + 'Notebooks', + 'Programming Languages', + 'SCM Providers', + 'Snippets', + 'Themes', + 'Testing', + 'Visualization', + 'Other', +]; export interface IExtensionManifest { readonly name: string; diff --git a/src/vs/platform/remote/browser/browserSocketFactory.ts b/src/vs/platform/remote/browser/browserSocketFactory.ts index ff112be60a..abb47b08d6 100644 --- a/src/vs/platform/remote/browser/browserSocketFactory.ts +++ b/src/vs/platform/remote/browser/browserSocketFactory.ts @@ -194,6 +194,9 @@ class BrowserSocket implements ISocket { this.socket.close(); } + public drain(): Promise { + return Promise.resolve(); + } } diff --git a/src/vs/platform/undoRedo/common/undoRedoService.ts b/src/vs/platform/undoRedo/common/undoRedoService.ts index 4431ca936c..67e5d6502f 100644 --- a/src/vs/platform/undoRedo/common/undoRedoService.ts +++ b/src/vs/platform/undoRedo/common/undoRedoService.ts @@ -82,10 +82,19 @@ class RemovedResources { let messages: string[] = []; if (externalRemoval.length > 0) { - messages.push(nls.localize('externalRemoval', "The following files have been closed and modified on disk: {0}.", externalRemoval.join(', '))); + messages.push( + nls.localize( + { key: 'externalRemoval', comment: ['{0} is a list of filenames'] }, + "The following files have been closed and modified on disk: {0}.", externalRemoval.join(', ') + ) + ); } if (noParallelUniverses.length > 0) { - messages.push(nls.localize('noParallelUniverses', "The following files have been modified in an incompatible way: {0}.", noParallelUniverses.join(', '))); + messages.push( + nls.localize( + { key: 'noParallelUniverses', comment: ['{0} is a list of filenames'] }, + "The following files have been modified in an incompatible way: {0}.", noParallelUniverses.join(', ') + )); } return messages.join('\n'); } @@ -771,10 +780,26 @@ export class UndoRedoService implements IUndoRedoService { private _checkWorkspaceUndo(strResource: string, element: WorkspaceStackElement, editStackSnapshot: EditStackSnapshot, checkInvalidatedResources: boolean): WorkspaceVerificationError | null { if (element.removedResources) { - return this._tryToSplitAndUndo(strResource, element, element.removedResources, nls.localize('cannotWorkspaceUndo', "Could not undo '{0}' across all files. {1}", element.label, element.removedResources.createMessage())); + return this._tryToSplitAndUndo( + strResource, + element, + element.removedResources, + nls.localize( + { key: 'cannotWorkspaceUndo', comment: ['{0} is a label for an operation. {1} is another message.'] }, + "Could not undo '{0}' across all files. {1}", element.label, element.removedResources.createMessage() + ) + ); } if (checkInvalidatedResources && element.invalidatedResources) { - return this._tryToSplitAndUndo(strResource, element, element.invalidatedResources, nls.localize('cannotWorkspaceUndo', "Could not undo '{0}' across all files. {1}", element.label, element.invalidatedResources.createMessage())); + return this._tryToSplitAndUndo( + strResource, + element, + element.invalidatedResources, + nls.localize( + { key: 'cannotWorkspaceUndo', comment: ['{0} is a label for an operation. {1} is another message.'] }, + "Could not undo '{0}' across all files. {1}", element.label, element.invalidatedResources.createMessage() + ) + ); } // this must be the last past element in all the impacted resources! @@ -785,7 +810,15 @@ export class UndoRedoService implements IUndoRedoService { } } if (cannotUndoDueToResources.length > 0) { - return this._tryToSplitAndUndo(strResource, element, null, nls.localize('cannotWorkspaceUndoDueToChanges', "Could not undo '{0}' across all files because changes were made to {1}", element.label, cannotUndoDueToResources.join(', '))); + return this._tryToSplitAndUndo( + strResource, + element, + null, + nls.localize( + { key: 'cannotWorkspaceUndoDueToChanges', comment: ['{0} is a label for an operation. {1} is a list of filenames.'] }, + "Could not undo '{0}' across all files because changes were made to {1}", element.label, cannotUndoDueToResources.join(', ') + ) + ); } const cannotLockDueToResources: string[] = []; @@ -795,12 +828,28 @@ export class UndoRedoService implements IUndoRedoService { } } if (cannotLockDueToResources.length > 0) { - return this._tryToSplitAndUndo(strResource, element, null, nls.localize('cannotWorkspaceUndoDueToInProgressUndoRedo', "Could not undo '{0}' across all files because there is already an undo or redo operation running on {1}", element.label, cannotLockDueToResources.join(', '))); + return this._tryToSplitAndUndo( + strResource, + element, + null, + nls.localize( + { key: 'cannotWorkspaceUndoDueToInProgressUndoRedo', comment: ['{0} is a label for an operation. {1} is a list of filenames.'] }, + "Could not undo '{0}' across all files because there is already an undo or redo operation running on {1}", element.label, cannotLockDueToResources.join(', ') + ) + ); } // check if new stack elements were added in the meantime... if (!editStackSnapshot.isValid()) { - return this._tryToSplitAndUndo(strResource, element, null, nls.localize('cannotWorkspaceUndoDueToInMeantimeUndoRedo', "Could not undo '{0}' across all files because an undo or redo operation occurred in the meantime", element.label)); + return this._tryToSplitAndUndo( + strResource, + element, + null, + nls.localize( + { key: 'cannotWorkspaceUndoDueToInMeantimeUndoRedo', comment: ['{0} is a label for an operation. {1} is a list of filenames.'] }, + "Could not undo '{0}' across all files because an undo or redo operation occurred in the meantime", element.label + ) + ); } return null; @@ -881,7 +930,10 @@ export class UndoRedoService implements IUndoRedoService { return; } if (editStack.locked) { - const message = nls.localize('cannotResourceUndoDueToInProgressUndoRedo', "Could not undo '{0}' because there is already an undo or redo operation running.", element.label); + const message = nls.localize( + { key: 'cannotResourceUndoDueToInProgressUndoRedo', comment: ['{0} is a label for an operation.'] }, + "Could not undo '{0}' because there is already an undo or redo operation running.", element.label + ); this._notificationService.info(message); return; } @@ -942,10 +994,26 @@ export class UndoRedoService implements IUndoRedoService { private _checkWorkspaceRedo(strResource: string, element: WorkspaceStackElement, editStackSnapshot: EditStackSnapshot, checkInvalidatedResources: boolean): WorkspaceVerificationError | null { if (element.removedResources) { - return this._tryToSplitAndRedo(strResource, element, element.removedResources, nls.localize('cannotWorkspaceRedo', "Could not redo '{0}' across all files. {1}", element.label, element.removedResources.createMessage())); + return this._tryToSplitAndRedo( + strResource, + element, + element.removedResources, + nls.localize( + { key: 'cannotWorkspaceRedo', comment: ['{0} is a label for an operation. {1} is another message.'] }, + "Could not redo '{0}' across all files. {1}", element.label, element.removedResources.createMessage() + ) + ); } if (checkInvalidatedResources && element.invalidatedResources) { - return this._tryToSplitAndRedo(strResource, element, element.invalidatedResources, nls.localize('cannotWorkspaceRedo', "Could not redo '{0}' across all files. {1}", element.label, element.invalidatedResources.createMessage())); + return this._tryToSplitAndRedo( + strResource, + element, + element.invalidatedResources, + nls.localize( + { key: 'cannotWorkspaceRedo', comment: ['{0} is a label for an operation. {1} is another message.'] }, + "Could not redo '{0}' across all files. {1}", element.label, element.invalidatedResources.createMessage() + ) + ); } // this must be the last future element in all the impacted resources! @@ -956,7 +1024,15 @@ export class UndoRedoService implements IUndoRedoService { } } if (cannotRedoDueToResources.length > 0) { - return this._tryToSplitAndRedo(strResource, element, null, nls.localize('cannotWorkspaceRedoDueToChanges', "Could not redo '{0}' across all files because changes were made to {1}", element.label, cannotRedoDueToResources.join(', '))); + return this._tryToSplitAndRedo( + strResource, + element, + null, + nls.localize( + { key: 'cannotWorkspaceRedoDueToChanges', comment: ['{0} is a label for an operation. {1} is a list of filenames.'] }, + "Could not redo '{0}' across all files because changes were made to {1}", element.label, cannotRedoDueToResources.join(', ') + ) + ); } const cannotLockDueToResources: string[] = []; @@ -966,12 +1042,28 @@ export class UndoRedoService implements IUndoRedoService { } } if (cannotLockDueToResources.length > 0) { - return this._tryToSplitAndRedo(strResource, element, null, nls.localize('cannotWorkspaceRedoDueToInProgressUndoRedo', "Could not redo '{0}' across all files because there is already an undo or redo operation running on {1}", element.label, cannotLockDueToResources.join(', '))); + return this._tryToSplitAndRedo( + strResource, + element, + null, + nls.localize( + { key: 'cannotWorkspaceRedoDueToInProgressUndoRedo', comment: ['{0} is a label for an operation. {1} is a list of filenames.'] }, + "Could not redo '{0}' across all files because there is already an undo or redo operation running on {1}", element.label, cannotLockDueToResources.join(', ') + ) + ); } // check if new stack elements were added in the meantime... if (!editStackSnapshot.isValid()) { - return this._tryToSplitAndRedo(strResource, element, null, nls.localize('cannotWorkspaceRedoDueToInMeantimeUndoRedo', "Could not redo '{0}' across all files because an undo or redo operation occurred in the meantime", element.label)); + return this._tryToSplitAndRedo( + strResource, + element, + null, + nls.localize( + { key: 'cannotWorkspaceRedoDueToInMeantimeUndoRedo', comment: ['{0} is a label for an operation. {1} is a list of filenames.'] }, + "Could not redo '{0}' across all files because an undo or redo operation occurred in the meantime", element.label + ) + ); } return null; @@ -1015,7 +1107,10 @@ export class UndoRedoService implements IUndoRedoService { return; } if (editStack.locked) { - const message = nls.localize('cannotResourceRedoDueToInProgressUndoRedo', "Could not redo '{0}' because there is already an undo or redo operation running.", element.label); + const message = nls.localize( + { key: 'cannotResourceRedoDueToInProgressUndoRedo', comment: ['{0} is a label for an operation.'] }, + "Could not redo '{0}' because there is already an undo or redo operation running.", element.label + ); this._notificationService.info(message); return; } diff --git a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts index fc4607e39a..e8141f1192 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts @@ -32,6 +32,7 @@ type AutoSyncErrorClassification = { const enablementKey = 'sync.enable'; const disableMachineEventuallyKey = 'sync.disableMachineEventually'; const sessionIdKey = 'sync.sessionId'; +const storeUrlKey = 'sync.storeUrl'; export class UserDataAutoSyncEnablementService extends Disposable { @@ -97,15 +98,20 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i this.syncTriggerDelayer = this._register(new Delayer(0)); if (userDataSyncStoreService.userDataSyncStore) { + + storageService.store(storeUrlKey, userDataSyncStoreService.userDataSyncStore.url.toString(), StorageScope.GLOBAL); + if (this.isEnabled()) { this.logService.info('Auto Sync is enabled.'); } else { this.logService.info('Auto Sync is disabled.'); } this.updateAutoSync(); + if (this.hasToDisableMachineEventually()) { this.disableMachineEventually(); } + this._register(userDataSyncAccountService.onDidChangeAccount(() => this.updateAutoSync())); this._register(userDataSyncStoreService.onDidChangeDonotMakeRequestsUntil(() => this.updateAutoSync())); this._register(Event.debounce(userDataSyncService.onDidChangeLocal, (last, source) => last ? [...last, source] : [source], 1000)(sources => this.triggerSync(sources, false))); diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 0f5256fa87..7ea0506c10 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -868,22 +868,6 @@ declare module 'vscode' { debugAdapterExecutable?(folder: WorkspaceFolder | undefined, token?: CancellationToken): ProviderResult; } - export interface DebugSession { - - /** - * Terminates the session. - */ - terminate(): Thenable; - } - - export interface DebugSession { - - /** - * Terminates the session. - */ - terminate(): Thenable; - } - export namespace debug { /** diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index ebdb130dd4..4c25b4e80f 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -262,14 +262,6 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb return Promise.reject(new Error('debug session not found')); } - public $terminateDebugSession(sessionId: DebugSessionUUID): Promise { - const session = this.debugService.getModel().getSession(sessionId, true); - if (session) { - return session.terminate(); - } - return Promise.reject(new Error('debug session not found')); - } - public $stopDebugging(sessionId: DebugSessionUUID | undefined): Promise { if (sessionId) { const session = this.debugService.getModel().getSession(sessionId, true); diff --git a/src/vs/workbench/api/browser/mainThreadEditor.ts b/src/vs/workbench/api/browser/mainThreadEditor.ts index 15585a6029..1f2fec5f86 100644 --- a/src/vs/workbench/api/browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/browser/mainThreadEditor.ts @@ -269,6 +269,14 @@ export class MainThreadTextEditor { } })); + const isValidCodeEditor = () => { + // Due to event timings, it is possible that there is a model change event not yet delivered to us. + // > e.g. a model change event is emitted to a listener which then decides to update editor options + // > In this case the editor configuration change event reaches us first. + // So simply check that the model is still attached to this code editor + return (this._codeEditor && this._codeEditor.getModel() === this._model); + }; + const updateProperties = (selectionChangeSource: string | null) => { // Some editor events get delivered faster than model content changes. This is // problematic, as this leads to editor properties reaching the extension host @@ -287,18 +295,30 @@ export class MainThreadTextEditor { this._codeEditorListeners.add(this._codeEditor.onDidChangeCursorSelection((e) => { // selection + if (!isValidCodeEditor()) { + return; + } updateProperties(e.source); })); - this._codeEditorListeners.add(this._codeEditor.onDidChangeConfiguration(() => { + this._codeEditorListeners.add(this._codeEditor.onDidChangeConfiguration((e) => { // options + if (!isValidCodeEditor()) { + return; + } updateProperties(null); })); this._codeEditorListeners.add(this._codeEditor.onDidLayoutChange(() => { // visibleRanges + if (!isValidCodeEditor()) { + return; + } updateProperties(null); })); this._codeEditorListeners.add(this._codeEditor.onDidScrollChange(() => { // visibleRanges + if (!isValidCodeEditor()) { + return; + } updateProperties(null); })); this._updatePropertiesNow(null); diff --git a/src/vs/workbench/api/browser/mainThreadExtensionService.ts b/src/vs/workbench/api/browser/mainThreadExtensionService.ts index 2153926289..e43eab305e 100644 --- a/src/vs/workbench/api/browser/mainThreadExtensionService.ts +++ b/src/vs/workbench/api/browser/mainThreadExtensionService.ts @@ -128,7 +128,7 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha } } - $onExtensionHostExit(code: number): void { + async $onExtensionHostExit(code: number): Promise { this._extensionService._onExtensionHostExit(code); } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 54c9232b02..76175cb3ef 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -799,7 +799,7 @@ export interface MainThreadExtensionServiceShape extends IDisposable { $onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void; $onExtensionActivationError(extensionId: ExtensionIdentifier, error: ExtensionActivationError): Promise; $onExtensionRuntimeError(extensionId: ExtensionIdentifier, error: SerializedError): void; - $onExtensionHostExit(code: number): void; + $onExtensionHostExit(code: number): Promise; } export interface SCMProviderFeatures { @@ -882,7 +882,6 @@ export interface MainThreadDebugServiceShape extends IDisposable { $stopDebugging(sessionId: DebugSessionUUID | undefined): Promise; $setDebugSessionName(id: DebugSessionUUID, name: string): void; $customDebugAdapterRequest(id: DebugSessionUUID, command: string, args: any): Promise; - $terminateDebugSession(id: DebugSessionUUID): Promise; $appendDebugConsole(value: string): void; $startBreakpointEvents(): void; $registerBreakpoints(breakpoints: Array): Promise; diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index a6b98be0a3..cd184fc1f8 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -957,10 +957,6 @@ export class ExtHostDebugSession implements vscode.DebugSession { public customRequest(command: string, args: any): Promise { return this._debugServiceProxy.$customDebugAdapterRequest(this._id, command, args); } - - public terminate(): Promise { - return this._debugServiceProxy.$terminateDebugSession(this._id); - } } export class ExtHostDebugConsole implements vscode.DebugConsole { diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 197031c565..3fd03a3778 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -557,7 +557,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme } // after tests have run, we shutdown the host - this._gracefulExit(error || (typeof failures === 'number' && failures > 0) ? 1 /* ERROR */ : 0 /* OK */); + this._testRunnerExit(error || (typeof failures === 'number' && failures > 0) ? 1 /* ERROR */ : 0 /* OK */); }; const runResult = testRunner!.run(extensionTestsPath, oldTestRunnerCallback); @@ -567,11 +567,11 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme runResult .then(() => { c(); - this._gracefulExit(0); + this._testRunnerExit(0); }) .catch((err: Error) => { e(err.toString()); - this._gracefulExit(1); + this._testRunnerExit(1); }); } }); @@ -579,24 +579,20 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme // Otherwise make sure to shutdown anyway even in case of an error else { - this._gracefulExit(1 /* ERROR */); + this._testRunnerExit(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; - } - + private _testRunnerExit(code: number): void { + // wait at most 5000ms for the renderer to confirm our exit request and for the renderer socket to drain + // (this is to ensure all outstanding messages reach the renderer) + const exitPromise = this._mainThreadExtensionsProxy.$onExtensionHostExit(code); + const drainPromise = this._extHostContext.drain(); + Promise.race([Promise.all([exitPromise, drainPromise]), timeout(5000)]).then(() => { this._hostUtils.exit(code); - }, 500); + }); } private _startExtensionHost(): Promise { diff --git a/src/vs/workbench/api/common/extHostRpcService.ts b/src/vs/workbench/api/common/extHostRpcService.ts index ec0b601a6d..531bb02786 100644 --- a/src/vs/workbench/api/common/extHostRpcService.ts +++ b/src/vs/workbench/api/common/extHostRpcService.ts @@ -18,12 +18,12 @@ export class ExtHostRpcService implements IExtHostRpcService { readonly getProxy: (identifier: ProxyIdentifier) => T; readonly set: (identifier: ProxyIdentifier, instance: R) => R; readonly assertRegistered: (identifiers: ProxyIdentifier[]) => void; + readonly drain: () => Promise; constructor(rpcProtocol: IRPCProtocol) { this.getProxy = rpcProtocol.getProxy.bind(rpcProtocol); this.set = rpcProtocol.set.bind(rpcProtocol); this.assertRegistered = rpcProtocol.assertRegistered.bind(rpcProtocol); - + this.drain = rpcProtocol.drain.bind(rpcProtocol); } - } diff --git a/src/vs/workbench/api/worker/extHostExtensionService.ts b/src/vs/workbench/api/worker/extHostExtensionService.ts index 31a39761d3..594d3c35db 100644 --- a/src/vs/workbench/api/worker/extHostExtensionService.ts +++ b/src/vs/workbench/api/worker/extHostExtensionService.ts @@ -51,7 +51,10 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { // fetch JS sources as text and create a new function around it const source = await response.text(); - const initFn = new Function('module', 'exports', 'require', `${source}\n//# sourceURL=${module.toString(true)}`); + // Here we append #vscode-extension to serve as a marker, such that source maps + // can be adjusted for the extra wrapping function. + const sourceURL = `${module.toString(true)}#vscode-extension`; + const initFn = new Function('module', 'exports', 'require', `${source}\n//# sourceURL=${sourceURL}`); // define commonjs globals: `module`, `exports`, and `require` const _exports = {}; diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index bb72d95d0b..e89b6aea4b 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -33,6 +33,7 @@ import { ContextSubMenu } from 'vs/base/browser/contextmenu'; import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; import { AuthenticationSession } from 'vs/editor/common/modes'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; export class ViewContainerActivityAction extends ActivityAction { @@ -98,6 +99,8 @@ export class ViewContainerActivityAction extends ActivityAction { } } +export const ACCOUNTS_VISIBILITY_PREFERENCE_KEY = 'workbench.activity.showAccounts'; + export class AccountsActionViewItem extends ActivityActionViewItem { constructor( action: ActivityAction, @@ -107,7 +110,8 @@ export class AccountsActionViewItem extends ActivityActionViewItem { @IMenuService protected menuService: IMenuService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IAuthenticationService private readonly authenticationService: IAuthenticationService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IStorageService private readonly storageService: IStorageService ) { super(action, { draggable: false, colors, icon: true }, themeService); } @@ -190,6 +194,15 @@ export class AccountsActionViewItem extends ActivityActionViewItem { } }); + if (menus.length) { + menus.push(new Separator()); + } + + menus.push(new Action('hide', nls.localize('hide', "Hide"), undefined, true, _ => { + this.storageService.store(ACCOUNTS_VISIBILITY_PREFERENCE_KEY, false, StorageScope.GLOBAL); + return Promise.resolve(); + })); + return menus; } diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 849eb76a1a..b5ed80a630 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -5,11 +5,10 @@ import 'vs/css!./media/activitybarpart'; import * as nls from 'vs/nls'; -import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionsOrientation, ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { GLOBAL_ACTIVITY_ID, IActivity, ACCOUNTS_ACTIIVTY_ID } from 'vs/workbench/common/activity'; import { Part } from 'vs/workbench/browser/part'; -import { GlobalActivityActionViewItem, ViewContainerActivityAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewContainerActivityAction, HomeAction, HomeActionViewItem } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; -import { AccountsActionViewItem } from 'sql/workbench/browser/parts/activitybar/activitybarActions'; // {{ SQL CARBON EDIT }} - use the ADS account management action +import { GlobalActivityActionViewItem, ViewContainerActivityAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewContainerActivityAction, HomeAction, HomeActionViewItem, ACCOUNTS_VISIBILITY_PREFERENCE_KEY } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; import { IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -43,6 +42,8 @@ import { Event } from 'vs/base/common/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; +import { AccountsActionViewItem } from 'sql/workbench/browser/parts/activitybar/activitybarActions'; // {{ SQL CARBON EDIT }} - use the ADS account management action + interface IPlaceholderViewContainer { id: string; name?: string; @@ -76,7 +77,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { static readonly PINNED_VIEW_CONTAINERS = 'workbench.activity.pinnedViewlets2'; private static readonly PLACEHOLDER_VIEW_CONTAINERS = 'workbench.activity.placeholderViewlets'; private static readonly HOME_BAR_VISIBILITY_PREFERENCE = 'workbench.activity.showHomeIndicator'; - + private static readonly ACCOUNTS_ACTION_INDEX = 0; //#region IView readonly minimumWidth: number = 48; @@ -165,6 +166,18 @@ export class ActivitybarPart extends Part implements IActivityBarService { actions.push(this.instantiationService.createInstance(ToggleMenuBarAction, ToggleMenuBarAction.ID, menuBarVisibility === 'compact' ? nls.localize('hideMenu', "Hide Menu") : nls.localize('showMenu', "Show Menu"))); } + const toggleAccountsVisibilityAction = new Action( + 'toggleAccountsVisibility', + nls.localize('accounts', "Accounts"), + undefined, + true, + async () => { this.accountsVisibilityPreference = !this.accountsVisibilityPreference; } + ); + + toggleAccountsVisibilityAction.checked = !!this.accountsActivityAction; + actions.push(toggleAccountsVisibilityAction); + actions.push(new Separator()); + actions.push(new Action( ToggleActivityBarVisibilityAction.ID, nls.localize('hideActivitBar', "Hide Activity Bar"), @@ -588,17 +601,35 @@ export class ActivitybarPart extends Part implements IActivityBarService { cssClass: Codicon.settingsGear.classNames }); - this.accountsActivityAction = new ActivityAction({ - id: 'workbench.actions.accounts', - name: nls.localize('accounts', "Accounts"), - cssClass: Codicon.account.classNames - }); + if (this.accountsVisibilityPreference) { + this.accountsActivityAction = new ActivityAction({ + id: 'workbench.actions.accounts', + name: nls.localize('accounts', "Accounts"), + cssClass: Codicon.account.classNames + }); - this.globalActivityActionBar.push(this.accountsActivityAction); + this.globalActivityActionBar.push(this.accountsActivityAction, { index: ActivitybarPart.ACCOUNTS_ACTION_INDEX }); + } this.globalActivityActionBar.push(this.globalActivityAction); } + private toggleAccountsActivity() { + if (this.globalActivityActionBar) { + if (this.accountsActivityAction) { + this.globalActivityActionBar.pull(ActivitybarPart.ACCOUNTS_ACTION_INDEX); + this.accountsActivityAction = undefined; + } else { + this.accountsActivityAction = new ActivityAction({ + id: 'workbench.actions.accounts', + name: nls.localize('accounts', "Accounts"), + cssClass: Codicon.account.classNames + }); + this.globalActivityActionBar.push(this.accountsActivityAction, { index: ActivitybarPart.ACCOUNTS_ACTION_INDEX }); + } + } + } + private getCompositeActions(compositeId: string): { activityAction: ViewContainerActivityAction, pinnedAction: ToggleCompositePinnedAction } { let compositeActions = this.compositeActions.get(compositeId); if (!compositeActions) { @@ -828,6 +859,10 @@ export class ActivitybarPart extends Part implements IActivityBarService { if (e.key === ActivitybarPart.HOME_BAR_VISIBILITY_PREFERENCE && e.scope === StorageScope.GLOBAL) { this.onDidChangeHomeBarVisibility(); } + + if (e.key === ACCOUNTS_VISIBILITY_PREFERENCE_KEY && e.scope === StorageScope.GLOBAL) { + this.toggleAccountsActivity(); + } } private saveCachedViewContainers(): void { @@ -965,6 +1000,14 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.storageService.store(ActivitybarPart.HOME_BAR_VISIBILITY_PREFERENCE, value, StorageScope.GLOBAL); } + private get accountsVisibilityPreference(): boolean { + return this.storageService.getBoolean(ACCOUNTS_VISIBILITY_PREFERENCE_KEY, StorageScope.GLOBAL, true); + } + + private set accountsVisibilityPreference(value: boolean) { + this.storageService.store(ACCOUNTS_VISIBILITY_PREFERENCE_KEY, value, StorageScope.GLOBAL); + } + private migrateFromOldCachedViewContainersValue(): void { const value = this.storageService.get('workbench.activity.pinnedViewlets', StorageScope.GLOBAL); if (value !== undefined) { diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index 38030cba3f..7570fe87ef 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -68,18 +68,18 @@ export abstract class Viewlet extends PaneComposite implements IViewlet { } getSecondaryActions(): IAction[] { - const viewSecondaryActions = this.viewPaneContainer.getViewsVisibilityActions(); + const viewVisibilityActions = this.viewPaneContainer.getViewsVisibilityActions(); const secondaryActions = this.viewPaneContainer.getSecondaryActions(); - if (viewSecondaryActions.length <= 1) { + if (viewVisibilityActions.length <= 1 || viewVisibilityActions.every(({ enabled }) => !enabled)) { return secondaryActions; } if (secondaryActions.length === 0) { - return viewSecondaryActions; + return viewVisibilityActions; } return [ - new ContextSubMenu(nls.localize('views', "Views"), viewSecondaryActions), + new ContextSubMenu(nls.localize('views', "Views"), viewVisibilityActions), new Separator(), ...secondaryActions ]; diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index c494ff9428..225c971bc8 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -689,7 +689,7 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { toJSON(): any { const result = super.toJSON(); - result.uri = this.uri; + result.uri = this._uri; result.lineNumber = this._lineNumber; result.column = this._column; result.adapterData = this.adapterData; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 8395a4c3ba..d0f002a21e 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -1775,6 +1775,29 @@ export class ShowPopularExtensionsAction extends Action { } } +export class RecentlyPublishedExtensionsAction extends Action { + + static readonly ID = 'workbench.extensions.action.recentlyPublishedExtensions'; + static readonly LABEL = localize('recentlyPublishedExtensions', "Recently Published Extensions"); + + constructor( + id: string, + label: string, + @IViewletService private readonly viewletService: IViewletService + ) { + super(id, label, undefined, true); + } + + run(): Promise { + return this.viewletService.openViewlet(VIEWLET_ID, true) + .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) + .then(viewlet => { + viewlet.search('@sort:publishedDate '); + viewlet.focus(); + }); + } +} + export class ShowRecommendedExtensionsAction extends Action { static readonly ID = 'workbench.extensions.action.showRecommendedExtensions'; @@ -2050,6 +2073,27 @@ export class ShowAzureExtensionsAction extends Action { } } +export class SearchCategoryAction extends Action { + + constructor( + id: string, + label: string, + private readonly category: string, + @IViewletService private readonly viewletService: IViewletService + ) { + super(id, label, undefined, true); + } + + run(): Promise { + return this.viewletService.openViewlet(VIEWLET_ID, true) + .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) + .then(viewlet => { + viewlet.search(`@category:"${this.category.toLowerCase()}"`); + viewlet.focus(); + }); + } +} + export class ChangeSortAction extends Action { private query: Query; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 2c89be7afd..cd7f965a66 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -11,23 +11,23 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { Event as EventOf, Emitter } from 'vs/base/common/event'; import { IAction, Action } from 'vs/base/common/actions'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Separator, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { append, $, addClass, toggleClass, Dimension, hide, show } from 'vs/base/browser/dom'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, AutoUpdateConfigurationKey, ShowRecommendationsOnlyOnDemandKey, CloseExtensionDetailsOnViewChangeKey } from '../common/extensions'; +import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, AutoUpdateConfigurationKey, CloseExtensionDetailsOnViewChangeKey } from '../common/extensions'; import { - ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowRecommendedExtensionsAction, /*ShowPopularExtensionsAction,*/ ShowDisabledExtensionsAction, - ShowOutdatedExtensionsAction, ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, CheckForUpdatesAction, DisableAllAction, EnableAllAction, - EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction + ShowRecommendedExtensionsAction, /*ShowPopularExtensionsAction,*/ + ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, CheckForUpdatesAction, DisableAllAction, EnableAllAction, + EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction, SearchCategoryAction, /*RecentlyPublishedExtensionsAction, */ShowInstalledExtensionsAction, ShowOutdatedExtensionsAction, ShowDisabledExtensionsAction, ShowEnabledExtensionsAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; -import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInExtensionsView, BuiltInThemesExtensionsView, BuiltInBasicsExtensionsView, ServerExtensionsView, DefaultRecommendedExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; +import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInFeatureExtensionsView, BuiltInThemesExtensionsView, BuiltInProgrammingLanguageExtensionsView, ServerExtensionsView, DefaultRecommendedExtensionsView, OutdatedExtensionsView, InstalledExtensionsView, SearchBuiltInExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import Severity from 'vs/base/common/severity'; @@ -50,9 +50,8 @@ import { SuggestEnabledInput, attachSuggestEnabledInputBoxStyler } from 'vs/work import { alert } from 'vs/base/browser/ui/aria/aria'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { ExtensionType, EXTENSION_CATEGORIES } from 'vs/platform/extensions/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { RemoteNameContext } from 'vs/workbench/browser/contextkeys'; import { ILabelService } from 'vs/platform/label/common/label'; import { MementoObject } from 'vs/workbench/common/memento'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -60,6 +59,8 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr import { DragAndDropObserver } from 'vs/workbench/browser/dnd'; import { URI } from 'vs/base/common/uri'; import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme'; +import { ContextSubMenu } from 'vs/base/browser/contextmenu'; +import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown'; const NonEmptyWorkspaceContext = new RawContextKey('nonEmptyWorkspace', false); const DefaultViewsContext = new RawContextKey('defaultExtensionViews', true); @@ -69,25 +70,9 @@ const SearchOutdatedExtensionsContext = new RawContextKey('searchOutdat const SearchEnabledExtensionsContext = new RawContextKey('searchEnabledExtensions', false); const SearchDisabledExtensionsContext = new RawContextKey('searchDisabledExtensions', false); const HasInstalledExtensionsContext = new RawContextKey('hasInstalledExtensions', true); +const BuiltInExtensionsContext = new RawContextKey('builtInExtensions', false); const SearchBuiltInExtensionsContext = new RawContextKey('searchBuiltInExtensions', false); const RecommendedExtensionsContext = new RawContextKey('recommendedExtensions', false); -const DefaultRecommendedExtensionsContext = new RawContextKey('defaultRecommendedExtensions', false); -const viewIdNameMappings: { [id: string]: string } = { - 'extensions.listView': localize('marketPlace', "Marketplace"), - 'extensions.enabledExtensionList': localize('enabledExtensions', "Enabled"), - 'extensions.enabledExtensionList2': localize('enabledExtensions', "Enabled"), - 'extensions.disabledExtensionList': localize('disabledExtensions', "Disabled"), - 'extensions.disabledExtensionList2': localize('disabledExtensions', "Disabled"), - // {{SQL CARBON EDIT}} - // 'extensions.popularExtensionsList': localize('popularExtensions', "Popular"), - 'extensions.recommendedList': localize('recommendedExtensions', "Recommended"), - 'extensions.otherrecommendedList': localize('otherRecommendedExtensions', "Other Recommendations"), - 'extensions.workspaceRecommendedList': localize('workspaceRecommendedExtensions', "Workspace Recommendations"), - 'extensions.builtInExtensionsList': localize('builtInExtensions', "Features"), - 'extensions.builtInThemesExtensionsList': localize('builtInThemesExtensions', "Themes"), - 'extensions.builtInBasicsExtensionsList': localize('builtInBasicsExtensions', "Programming Languages"), - 'extensions.syncedExtensionsList': localize('syncedExtensions', "My Account"), -}; export class ExtensionsViewletViewsContribution implements IWorkbenchContribution { @@ -103,223 +88,233 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio } private registerViews(): void { - let viewDescriptors: IViewDescriptor[] = []; - viewDescriptors.push(this.createMarketPlaceExtensionsListViewDescriptor()); - viewDescriptors.push(this.createDefaultEnabledExtensionsListViewDescriptor()); - viewDescriptors.push(this.createDefaultDisabledExtensionsListViewDescriptor()); - // {{SQL CARBON EDIT}} - // viewDescriptors.push(this.createDefaultPopularExtensionsListViewDescriptor()); - viewDescriptors.push(this.createEnabledExtensionsListViewDescriptor()); - viewDescriptors.push(this.createDisabledExtensionsListViewDescriptor()); - viewDescriptors.push(this.createBuiltInExtensionsListViewDescriptor()); - viewDescriptors.push(this.createBuiltInBasicsExtensionsListViewDescriptor()); - viewDescriptors.push(this.createBuiltInThemesExtensionsListViewDescriptor()); - viewDescriptors.push(this.createDefaultRecommendedExtensionsListViewDescriptor()); - viewDescriptors.push(this.createOtherRecommendedExtensionsListViewDescriptor()); - viewDescriptors.push(this.createWorkspaceRecommendedExtensionsListViewDescriptor()); + const viewDescriptors: IViewDescriptor[] = []; - if (this.extensionManagementServerService.localExtensionManagementServer) { - viewDescriptors.push(...this.createExtensionsViewDescriptorsForServer(this.extensionManagementServerService.localExtensionManagementServer)); - } - if (this.extensionManagementServerService.remoteExtensionManagementServer) { - viewDescriptors.push(...this.createExtensionsViewDescriptorsForServer(this.extensionManagementServerService.remoteExtensionManagementServer)); - } + /* Default views */ + viewDescriptors.push(...this.createDefaultExtensionsViewDescriptors()); + + /* Search views */ + viewDescriptors.push(...this.createSearchExtensionsViewDescriptors()); + + /* Recommendations views */ + viewDescriptors.push(...this.createRecommendedExtensionsViewDescriptors()); + + /* Built-in extensions views */ + viewDescriptors.push(...this.createBuiltinExtensionsViewDescriptors()); Registry.as(Extensions.ViewsRegistry).registerViews(viewDescriptors, this.container); } - // View used for any kind of searching - private createMarketPlaceExtensionsListViewDescriptor(): IViewDescriptor { - const id = 'extensions.listView'; - return { - id, - name: viewIdNameMappings[id], + private createDefaultExtensionsViewDescriptors(): IViewDescriptor[] { + const viewDescriptors: IViewDescriptor[] = []; + + /* + * Default popular extensions view + * Separate view for popular extensions required as we need to show popular and recommended sections + * in the default view when there is no search text, and user has no installed extensions. + */ + // viewDescriptors.push({ {{SQL CARBON EDIT}} remove popular + // id: 'workbench.views.extensions.popular', + // name: localize('popularExtensions', "Popular"), + // ctorDescriptor: new SyncDescriptor(ExtensionsListView), + // when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.not('hasInstalledExtensions')), + // weight: 60, + // order: 1, + // }); + + /* + * Default installed extensions views - Shows all user installed extensions. + */ + const servers: IExtensionManagementServer[] = []; + if (this.extensionManagementServerService.localExtensionManagementServer) { + servers.push(this.extensionManagementServerService.localExtensionManagementServer); + } + if (this.extensionManagementServerService.remoteExtensionManagementServer) { + servers.push(this.extensionManagementServerService.remoteExtensionManagementServer); + } + const getViewName = (viewTitle: string, server: IExtensionManagementServer): string => { + return servers.length > 1 ? `${server.label} - ${viewTitle}` : viewTitle; + }; + for (const server of servers) { + const getInstalledViewName = (): string => getViewName(localize('installed', "Installed"), server); + const onDidChangeServerLabel: EventOf = EventOf.map(this.labelService.onDidChangeFormatters, () => undefined); + viewDescriptors.push({ + id: servers.length > 1 ? `workbench.views.extensions.${server.id}.installed` : `workbench.views.extensions.installed`, + get name() { return getInstalledViewName(); }, + ctorDescriptor: new SyncDescriptor(ServerExtensionsView, [server, EventOf.map(onDidChangeServerLabel, () => getInstalledViewName())]), + when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions')), + weight: 100, + order: 2, + /* Installed extensions views shall not be hidden when there are more than one server */ + canToggleVisibility: servers.length === 1 + }); + } + + /* + * Default recommended extensions view + * When user has installed extensions, this is shown along with the views for enabled & disabled extensions + * When user has no installed extensions, this is shown along with the view for popular extensions + */ + viewDescriptors.push({ + id: 'extensions.recommendedList', + name: localize('recommendedExtensions', "Recommended"), + ctorDescriptor: new SyncDescriptor(DefaultRecommendedExtensionsView), + when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.not('config.extensions.showRecommendationsOnlyOnDemand')), + weight: 40, + order: 3, + canToggleVisibility: true + }); + + /* Installed views shall be default in multi server window */ + if (servers.length === 1) { + /* + * Default enabled extensions view - Shows all user installed enabled extensions. + * Hidden by default + */ + viewDescriptors.push({ + id: 'workbench.views.extensions.enabled', + name: localize('enabledExtensions', "Enabled"), + ctorDescriptor: new SyncDescriptor(EnabledExtensionsView), + when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions')), + hideByDefault: true, + weight: 40, + order: 4, + canToggleVisibility: true + }); + + /* + * Default disabled extensions view - Shows all disabled extensions. + * Hidden by default + */ + viewDescriptors.push({ + id: 'workbench.views.extensions.disabled', + name: localize('disabledExtensions', "Disabled"), + ctorDescriptor: new SyncDescriptor(DisabledExtensionsView), + when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions')), + hideByDefault: true, + weight: 10, + order: 5, + canToggleVisibility: true + }); + + } + + return viewDescriptors; + } + + private createSearchExtensionsViewDescriptors(): IViewDescriptor[] { + const viewDescriptors: IViewDescriptor[] = []; + + /* + * View used for searching Marketplace + */ + viewDescriptors.push({ + id: 'workbench.views.extensions.marketplace', + name: localize('marketPlace', "Marketplace"), ctorDescriptor: new SyncDescriptor(ExtensionsListView), when: ContextKeyExpr.and(ContextKeyExpr.has('searchMarketplaceExtensions')), - weight: 100 - }; - } + }); - // Separate view for enabled extensions required as we need to show enabled, disabled and recommended sections - // in the default view when there is no search text, but user has installed extensions. - private createDefaultEnabledExtensionsListViewDescriptor(): IViewDescriptor { - const id = 'extensions.enabledExtensionList'; - return { - id, - name: viewIdNameMappings[id], - ctorDescriptor: new SyncDescriptor(EnabledExtensionsView), - when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteNameContext.isEqualTo('')), - weight: 40, - canToggleVisibility: true, - order: 1 - }; - } - - // Separate view for disabled extensions required as we need to show enabled, disabled and recommended sections - // in the default view when there is no search text, but user has installed extensions. - private createDefaultDisabledExtensionsListViewDescriptor(): IViewDescriptor { - const id = 'extensions.disabledExtensionList'; - return { - id, - name: viewIdNameMappings[id], - ctorDescriptor: new SyncDescriptor(DisabledExtensionsView), - when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteNameContext.isEqualTo('')), - weight: 10, - canToggleVisibility: true, - order: 3, - collapsed: true - }; - } - - /* // {{SQL CARBON EDIT}} - // Separate view for popular extensions required as we need to show popular and recommended sections - // in the default view when there is no search text, and user has no installed extensions. - private createDefaultPopularExtensionsListViewDescriptor(): IViewDescriptor { - const id = 'extensions.popularExtensionsList'; - return { - id, - name: viewIdNameMappings[id], - ctorDescriptor: new SyncDescriptor(ExtensionsListView), - when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.not('hasInstalledExtensions')), - weight: 60, - order: 1 - }; - } - */ - - private createExtensionsViewDescriptorsForServer(server: IExtensionManagementServer): IViewDescriptor[] { - const getViewName = (viewTitle: string, server: IExtensionManagementServer): string => { - const serverLabel = server.label; - if (viewTitle && this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { - return `${serverLabel} - ${viewTitle}`; - } - return viewTitle ? viewTitle : serverLabel; - }; - const getInstalledViewName = (): string => getViewName(localize('installed', "Installed"), server); - const getOutdatedViewName = (): string => getViewName(localize('outdated', "Outdated"), server); - const onDidChangeServerLabel: EventOf = EventOf.map(this.labelService.onDidChangeFormatters, () => undefined); - return [{ - id: `extensions.${server.id}.installed`, - get name() { return getInstalledViewName(); }, - ctorDescriptor: new SyncDescriptor(ServerExtensionsView, [server, EventOf.map(onDidChangeServerLabel, () => getInstalledViewName())]), + /* + * View used for searching all installed extensions + */ + viewDescriptors.push({ + id: 'workbench.views.extensions.searchInstalled', + name: localize('installed', "Installed"), + ctorDescriptor: new SyncDescriptor(InstalledExtensionsView), when: ContextKeyExpr.and(ContextKeyExpr.has('searchInstalledExtensions')), - weight: 100 - }, { - id: `extensions.${server.id}.outdated`, - get name() { return getOutdatedViewName(); }, - ctorDescriptor: new SyncDescriptor(ServerExtensionsView, [server, EventOf.map(onDidChangeServerLabel, () => getOutdatedViewName())]), - when: ContextKeyExpr.and(ContextKeyExpr.has('searchOutdatedExtensions')), - weight: 100 - }, { - id: `extensions.${server.id}.default`, - get name() { return getInstalledViewName(); }, - ctorDescriptor: new SyncDescriptor(ServerExtensionsView, [server, EventOf.map(onDidChangeServerLabel, () => getInstalledViewName())]), - when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteNameContext.notEqualsTo('')), - weight: 40, - order: 1 - }]; - } + }); - // Separate view for recommended extensions required as we need to show it along with other views when there is no search text. - // When user has installed extensions, this is shown along with the views for enabled & disabled extensions - // When user has no installed extensions, this is shown along with the view for popular extensions - private createDefaultRecommendedExtensionsListViewDescriptor(): IViewDescriptor { - const id = 'extensions.recommendedList'; - return { - id, - name: viewIdNameMappings[id], - ctorDescriptor: new SyncDescriptor(DefaultRecommendedExtensionsView), - when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('defaultRecommendedExtensions')), - weight: 40, - order: 2, - canToggleVisibility: true - }; - } - - // Separate view for recommedations that are not workspace recommendations. - // Shown along with view for workspace recommendations, when using the command that shows recommendations - private createOtherRecommendedExtensionsListViewDescriptor(): IViewDescriptor { - const id = 'extensions.otherrecommendedList'; - return { - id, - name: viewIdNameMappings[id], - ctorDescriptor: new SyncDescriptor(RecommendedExtensionsView), - when: ContextKeyExpr.has('recommendedExtensions'), - weight: 50, - order: 2 - }; - } - - // Separate view for workspace recommendations. - // Shown along with view for other recommendations, when using the command that shows recommendations - private createWorkspaceRecommendedExtensionsListViewDescriptor(): IViewDescriptor { - const id = 'extensions.workspaceRecommendedList'; - return { - id, - name: viewIdNameMappings[id], - ctorDescriptor: new SyncDescriptor(WorkspaceRecommendedExtensionsView), - when: ContextKeyExpr.and(ContextKeyExpr.has('recommendedExtensions'), ContextKeyExpr.has('nonEmptyWorkspace')), - weight: 50, - order: 1 - }; - } - - private createEnabledExtensionsListViewDescriptor(): IViewDescriptor { - const id = 'extensions.enabledExtensionList2'; - return { - id, - name: viewIdNameMappings[id], + /* + * View used for searching enabled extensions + */ + viewDescriptors.push({ + id: 'workbench.views.extensions.searchEnabled', + name: localize('enabled', "Enabled"), ctorDescriptor: new SyncDescriptor(EnabledExtensionsView), when: ContextKeyExpr.and(ContextKeyExpr.has('searchEnabledExtensions')), - weight: 40, - order: 1 - }; - } + }); - private createDisabledExtensionsListViewDescriptor(): IViewDescriptor { - const id = 'extensions.disabledExtensionList2'; - return { - id, - name: viewIdNameMappings[id], + /* + * View used for searching disabled extensions + */ + viewDescriptors.push({ + id: 'workbench.views.extensions.searchDisabled', + name: localize('disabled', "Disabled"), ctorDescriptor: new SyncDescriptor(DisabledExtensionsView), when: ContextKeyExpr.and(ContextKeyExpr.has('searchDisabledExtensions')), - weight: 10, - order: 3, - collapsed: true - }; + }); + + /* + * View used for searching outdated extensions + */ + viewDescriptors.push({ + id: 'workbench.views.extensions.searchOutdated', + name: localize('outdated', "Outdated"), + ctorDescriptor: new SyncDescriptor(OutdatedExtensionsView), + when: ContextKeyExpr.and(ContextKeyExpr.has('searchOutdatedExtensions')), + }); + + /* + * View used for searching builtin extensions + */ + viewDescriptors.push({ + id: 'workbench.views.extensions.searchBuiltin', + name: localize('builtin', "Builtin"), + ctorDescriptor: new SyncDescriptor(SearchBuiltInExtensionsView), + when: ContextKeyExpr.and(ContextKeyExpr.has('searchBuiltInExtensions')), + }); + + return viewDescriptors; } - private createBuiltInExtensionsListViewDescriptor(): IViewDescriptor { - const id = 'extensions.builtInExtensionsList'; - return { - id, - name: viewIdNameMappings[id], - ctorDescriptor: new SyncDescriptor(BuiltInExtensionsView), - when: ContextKeyExpr.has('searchBuiltInExtensions'), - weight: 100 - }; + private createRecommendedExtensionsViewDescriptors(): IViewDescriptor[] { + const viewDescriptors: IViewDescriptor[] = []; + + viewDescriptors.push({ + id: 'workbench.views.extensions.workspaceRecommendations', + name: localize('workspaceRecommendedExtensions', "Workspace Recommendations"), + ctorDescriptor: new SyncDescriptor(WorkspaceRecommendedExtensionsView), + when: ContextKeyExpr.and(ContextKeyExpr.has('recommendedExtensions'), ContextKeyExpr.has('nonEmptyWorkspace')), + order: 1 + }); + + viewDescriptors.push({ + id: 'workbench.views.extensions.otherRecommendations', + name: localize('otherRecommendedExtensions', "Other Recommendations"), + ctorDescriptor: new SyncDescriptor(RecommendedExtensionsView), + when: ContextKeyExpr.has('recommendedExtensions'), + order: 2 + }); + + return viewDescriptors; } - private createBuiltInThemesExtensionsListViewDescriptor(): IViewDescriptor { - const id = 'extensions.builtInThemesExtensionsList'; - return { - id, - name: viewIdNameMappings[id], + private createBuiltinExtensionsViewDescriptors(): IViewDescriptor[] { + const viewDescriptors: IViewDescriptor[] = []; + + viewDescriptors.push({ + id: 'workbench.views.extensions.builtinFeatureExtensions', + name: localize('builtinFeatureExtensions', "Features"), + ctorDescriptor: new SyncDescriptor(BuiltInFeatureExtensionsView), + when: ContextKeyExpr.has('builtInExtensions'), + }); + + viewDescriptors.push({ + id: 'workbench.views.extensions.builtinThemeExtensions', + name: localize('builtInThemesExtensions', "Themes"), ctorDescriptor: new SyncDescriptor(BuiltInThemesExtensionsView), - when: ContextKeyExpr.has('searchBuiltInExtensions'), - weight: 100 - }; - } + when: ContextKeyExpr.has('builtInExtensions'), + }); - private createBuiltInBasicsExtensionsListViewDescriptor(): IViewDescriptor { - const id = 'extensions.builtInBasicsExtensionsList'; - return { - id, - name: viewIdNameMappings[id], - ctorDescriptor: new SyncDescriptor(BuiltInBasicsExtensionsView), - when: ContextKeyExpr.has('searchBuiltInExtensions'), - weight: 100 - }; + viewDescriptors.push({ + id: 'workbench.views.extensions.builtinProgrammingLanguageExtensions', + name: localize('builtinProgrammingLanguageExtensions', "Programming Languages"), + ctorDescriptor: new SyncDescriptor(BuiltInProgrammingLanguageExtensionsView), + when: ContextKeyExpr.has('builtInExtensions'), + }); + + return viewDescriptors; } } @@ -336,15 +331,13 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE private searchEnabledExtensionsContextKey: IContextKey; private searchDisabledExtensionsContextKey: IContextKey; private hasInstalledExtensionsContextKey: IContextKey; + private builtInExtensionsContextKey: IContextKey; private searchBuiltInExtensionsContextKey: IContextKey; private recommendedExtensionsContextKey: IContextKey; - private defaultRecommendedExtensionsContextKey: IContextKey; private searchDelayer: Delayer; private root: HTMLElement | undefined; private searchBox: SuggestEnabledInput | undefined; - private primaryActions: IAction[] | undefined; - private secondaryActions: IAction[] | null = null; private readonly searchViewletState: MementoObject; constructor( @@ -364,7 +357,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IPreferencesService private readonly preferencesService: IPreferencesService + @IPreferencesService private readonly preferencesService: IPreferencesService, ) { super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); @@ -377,10 +370,9 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE this.searchEnabledExtensionsContextKey = SearchEnabledExtensionsContext.bindTo(contextKeyService); this.searchDisabledExtensionsContextKey = SearchDisabledExtensionsContext.bindTo(contextKeyService); this.hasInstalledExtensionsContextKey = HasInstalledExtensionsContext.bindTo(contextKeyService); + this.builtInExtensionsContextKey = BuiltInExtensionsContext.bindTo(contextKeyService); this.searchBuiltInExtensionsContextKey = SearchBuiltInExtensionsContext.bindTo(contextKeyService); this.recommendedExtensionsContextKey = RecommendedExtensionsContext.bindTo(contextKeyService); - this.defaultRecommendedExtensionsContextKey = DefaultRecommendedExtensionsContext.bindTo(contextKeyService); - this.defaultRecommendedExtensionsContextKey.set(!this.configurationService.getValue(ShowRecommendationsOnlyOnDemandKey)); this._register(this.viewletService.onDidViewletOpen(this.onViewletOpen, this)); this.searchViewletState = this.getMemento(StorageScope.WORKSPACE); @@ -390,12 +382,8 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(AutoUpdateConfigurationKey)) { - this.secondaryActions = null; this.updateTitleArea(); } - if (e.affectedKeys.indexOf(ShowRecommendationsOnlyOnDemandKey) > -1) { - this.defaultRecommendedExtensionsContextKey.set(!this.configurationService.getValue(ShowRecommendationsOnlyOnDemandKey)); - } }, this)); } @@ -509,40 +497,57 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE } getActions(): IAction[] { - if (!this.primaryActions) { - this.primaryActions = [ - this.instantiationService.createInstance(ClearExtensionsInputAction, ClearExtensionsInputAction.ID, ClearExtensionsInputAction.LABEL, this.onSearchChange, this.searchBox ? this.searchBox.getValue() : '') - ]; + return [ + new Action('workbench.extensions.action.filterExtensions', localize('filterExtensions', "Filter Extensions..."), 'codicon-filter', true), + this.instantiationService.createInstance(ClearExtensionsInputAction, ClearExtensionsInputAction.ID, ClearExtensionsInputAction.LABEL, this.onSearchChange, this.searchBox ? this.searchBox.getValue() : ''), + ]; + } + + getActionViewItem(action: IAction): IActionViewItem | undefined { + if (action.id === 'workbench.extensions.action.filterExtensions') { + return new DropdownMenuActionViewItem(action, + [ + // this.instantiationService.createInstance(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, localize('most popular filter', "Most Popular")), // {{SQL CARBON EDIT}} + // this.instantiationService.createInstance(RecentlyPublishedExtensionsAction, RecentlyPublishedExtensionsAction.ID, localize('recently published filter', "Recently Published")), // {{SQL CARBON EDIT}} + this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('recomended filter', "Recommended")), + new ContextSubMenu(localize('filter by category', "Category"), EXTENSION_CATEGORIES.map(category => this.instantiationService.createInstance(SearchCategoryAction, `extensions.actions.searchByCategory.${category}`, category, category))), + + new Separator(), + this.instantiationService.createInstance(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, localize('builtin filter', "Built-in")), + this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, localize('installed filter', "Installed")), + this.instantiationService.createInstance(ShowEnabledExtensionsAction, ShowEnabledExtensionsAction.ID, localize('enabled filter', "Enabled")), + this.instantiationService.createInstance(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, localize('disabled filter', "Disabled")), + this.instantiationService.createInstance(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, localize('outdated filter', "Outdated")), + + new Separator(), + new ContextSubMenu(localize('sorty by', "Sort By"), [ + // this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.install', localize('sort by installs', "Install Count"), this.onSearchChange, 'installs'), // {{SQL CARBON EDIT}} + // this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.rating', localize('sort by rating', "Rating"), this.onSearchChange, 'rating'), // {{SQL CARBON EDIT}} + this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.name', localize('sort by name', "Name"), this.onSearchChange, 'name'), + this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.publishedDate', localize('sort by date', "Published Date"), this.onSearchChange, 'publishedDate'), + ]), + ], + this.contextMenuService, undefined, undefined, undefined, 'codicon-filter', undefined, true); } - return this.primaryActions; + return super.getActionViewItem(action); } getSecondaryActions(): IAction[] { - if (!this.secondaryActions) { - this.secondaryActions = [ - this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL), - this.instantiationService.createInstance(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, ShowOutdatedExtensionsAction.LABEL), - this.instantiationService.createInstance(ShowEnabledExtensionsAction, ShowEnabledExtensionsAction.ID, ShowEnabledExtensionsAction.LABEL), - this.instantiationService.createInstance(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, ShowDisabledExtensionsAction.LABEL), - this.instantiationService.createInstance(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, ShowBuiltInExtensionsAction.LABEL), - this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL), - // this.instantiationService.createInstance(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, ShowPopularExtensionsAction.LABEL), // {{SQL CARBON EDIT}} - new Separator(), - // {{SQL CARBON EDIT}} - //this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.install', localize('sort by installs', "Sort By: Install Count"), this.onSearchChange, 'installs'), - //this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.rating', localize('sort by rating', "Sort By: Rating"), this.onSearchChange, 'rating'), - this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.name', localize('sort by name', "Sort By: Name"), this.onSearchChange, 'name'), - new Separator(), - this.instantiationService.createInstance(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL), - ...(this.configurationService.getValue(AutoUpdateConfigurationKey) ? [this.instantiationService.createInstance(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL)] : [this.instantiationService.createInstance(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL), this.instantiationService.createInstance(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL)]), - this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL), - new Separator(), - this.instantiationService.createInstance(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL), - this.instantiationService.createInstance(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL) - ]; - } + const actions: IAction[] = []; - return this.secondaryActions; + actions.push(this.instantiationService.createInstance(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL)); + if (this.configurationService.getValue(AutoUpdateConfigurationKey)) { + actions.push(this.instantiationService.createInstance(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL)); + } else { + actions.push(this.instantiationService.createInstance(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL), this.instantiationService.createInstance(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL)); + } + actions.push(this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL)); + + actions.push(new Separator()); + actions.push(this.instantiationService.createInstance(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL)); + actions.push(this.instantiationService.createInstance(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL)); + + return actions; } search(value: string, refresh: boolean = false): void { @@ -580,7 +585,8 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE this.searchOutdatedExtensionsContextKey.set(ExtensionsListView.isOutdatedExtensionsQuery(value)); this.searchEnabledExtensionsContextKey.set(ExtensionsListView.isEnabledExtensionsQuery(value)); this.searchDisabledExtensionsContextKey.set(ExtensionsListView.isDisabledExtensionsQuery(value)); - this.searchBuiltInExtensionsContextKey.set(ExtensionsListView.isBuiltInExtensionsQuery(value)); + this.searchBuiltInExtensionsContextKey.set(ExtensionsListView.isSearchBuiltInExtensionsQuery(value)); + this.builtInExtensionsContextKey.set(ExtensionsListView.isBuiltInExtensionsQuery(value)); this.recommendedExtensionsContextKey.set(isRecommendedExtensionsQuery); this.searchMarketplaceExtensionsContextKey.set(!!value && !ExtensionsListView.isLocalExtensionsQuery(value) && !isRecommendedExtensionsQuery); this.nonEmptyWorkspaceContextKey.set(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY); @@ -602,19 +608,20 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE } private alertSearchResult(count: number, viewId: string): void { + const view = this.viewContainerModel.visibleViewDescriptors.find(view => view.id === viewId); switch (count) { case 0: break; case 1: - if (viewIdNameMappings[viewId]) { - alert(localize('extensionFoundInSection', "1 extension found in the {0} section.", viewIdNameMappings[viewId])); + if (view) { + alert(localize('extensionFoundInSection', "1 extension found in the {0} section.", view.name)); } else { alert(localize('extensionFound', "1 extension found.")); } break; default: - if (viewIdNameMappings[viewId]) { - alert(localize('extensionsFoundInSection', "{0} extensions found in the {1} section.", count, viewIdNameMappings[viewId])); + if (view) { + alert(localize('extensionsFoundInSection', "{0} extensions found in the {1} section.", count, view.name)); } else { alert(localize('extensionsFound', "{0} extensions found.", count)); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index d663d3289d..aa6d055a3f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -197,6 +197,7 @@ export class ExtensionsListView extends ViewPane { case 'installs': options = assign(options, { sortBy: SortBy.InstallCount }); break; case 'rating': options = assign(options, { sortBy: SortBy.WeightedRating }); break; case 'name': options = assign(options, { sortBy: SortBy.Title }); break; + case 'publishedDate': options = assign(options, { sortBy: SortBy.PublishedDate }); break; } const successCallback = (model: IPagedModel) => { @@ -882,16 +883,21 @@ export class ExtensionsListView extends ViewPane { this.list = null; } - static isBuiltInExtensionsQuery(query: string): boolean { - return /^\s*@builtin\s*$/i.test(query); - } - static isLocalExtensionsQuery(query: string): boolean { return this.isInstalledExtensionsQuery(query) || this.isOutdatedExtensionsQuery(query) || this.isEnabledExtensionsQuery(query) || this.isDisabledExtensionsQuery(query) - || this.isBuiltInExtensionsQuery(query); + || this.isBuiltInExtensionsQuery(query) + || this.isSearchBuiltInExtensionsQuery(query); + } + + static isSearchBuiltInExtensionsQuery(query: string): boolean { + return /@builtin\s.+/i.test(query); + } + + static isBuiltInExtensionsQuery(query: string): boolean { + return /@builtin$/i.test(query.trim()); } static isInstalledExtensionsQuery(query: string): boolean { @@ -977,7 +983,7 @@ export class ServerExtensionsView extends ExtensionsListView { async show(query: string): Promise> { query = query ? query : '@installed'; - if (!ExtensionsListView.isLocalExtensionsQuery(query) && !ExtensionsListView.isBuiltInExtensionsQuery(query)) { + if (!ExtensionsListView.isLocalExtensionsQuery(query)) { query = query += ' @installed'; } return super.show(query.trim()); @@ -1009,7 +1015,29 @@ export class DisabledExtensionsView extends ExtensionsListView { } } -export class BuiltInExtensionsView extends ExtensionsListView { +export class OutdatedExtensionsView extends ExtensionsListView { + + async show(query: string): Promise> { + query = query || '@outdated'; + return ExtensionsListView.isOutdatedExtensionsQuery(query) ? super.show(query) : this.showEmptyModel(); + } +} + +export class InstalledExtensionsView extends ExtensionsListView { + + async show(query: string): Promise> { + query = query || '@installed'; + return ExtensionsListView.isInstalledExtensionsQuery(query) ? super.show(query) : this.showEmptyModel(); + } +} + +export class SearchBuiltInExtensionsView extends ExtensionsListView { + async show(query: string): Promise> { + return ExtensionsListView.isSearchBuiltInExtensionsQuery(query) ? super.show(query) : this.showEmptyModel(); + } +} + +export class BuiltInFeatureExtensionsView extends ExtensionsListView { async show(query: string): Promise> { return (query && query.trim() !== '@builtin') ? this.showEmptyModel() : super.show('@builtin:features'); } @@ -1021,7 +1049,7 @@ export class BuiltInThemesExtensionsView extends ExtensionsListView { } } -export class BuiltInBasicsExtensionsView extends ExtensionsListView { +export class BuiltInProgrammingLanguageExtensionsView extends ExtensionsListView { async show(query: string): Promise> { return (query && query.trim() !== '@builtin') ? this.showEmptyModel() : super.show('@builtin:basics'); } diff --git a/src/vs/workbench/contrib/notebook/browser/constants.ts b/src/vs/workbench/contrib/notebook/browser/constants.ts index 088d2871ce..1be1d63b2a 100644 --- a/src/vs/workbench/contrib/notebook/browser/constants.ts +++ b/src/vs/workbench/contrib/notebook/browser/constants.ts @@ -13,7 +13,8 @@ export const CELL_RUN_GUTTER = 28; export const CODE_CELL_LEFT_MARGIN = 32; export const EDITOR_TOOLBAR_HEIGHT = 0; -export const BOTTOM_CELL_TOOLBAR_HEIGHT = 28; +export const BOTTOM_CELL_TOOLBAR_HEIGHT = 18; +export const BOTTOM_CELL_TOOLBAR_OFFSET = 12; export const CELL_STATUSBAR_HEIGHT = 22; // Margin above editor diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index 53033bd71c..5c85088648 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -74,7 +74,8 @@ const FOCUS_OUT_OUTPUT_COMMAND_ID = 'notebook.cell.focusOutOutput'; export const NOTEBOOK_ACTIONS_CATEGORY = { value: localize('notebookActions.category', "Notebook"), original: 'Notebook' }; -export const CELL_TITLE_GROUP_ID = 'inline'; +export const CELL_TITLE_CELL_GROUP_ID = 'inline/cell'; +export const CELL_TITLE_OUTPUT_GROUP_ID = 'inline/output'; const EDITOR_WIDGET_ACTION_WEIGHT = KeybindingWeight.EditorContrib; // smaller than Suggest Widget, etc @@ -407,6 +408,11 @@ registerAction2(class extends NotebookCellAction { weight: KeybindingWeight.WorkbenchContrib }, precondition: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR), + menu: { + id: MenuId.NotebookCellTitle, + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE), + group: '2_edit', + } }); } @@ -425,6 +431,11 @@ registerAction2(class extends NotebookCellAction { primary: KeyCode.KEY_M, weight: KeybindingWeight.WorkbenchContrib }, + menu: { + id: MenuId.NotebookCellTitle, + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE), + group: '2_edit', + } }); } @@ -601,7 +612,7 @@ registerAction2(class extends NotebookCellAction { NOTEBOOK_CELL_MARKDOWN_EDIT_MODE.toNegated(), NOTEBOOK_CELL_EDITABLE), order: CellToolbarOrder.EditCell, - group: CELL_TITLE_GROUP_ID + group: CELL_TITLE_CELL_GROUP_ID }, icon: { id: 'codicon/pencil' } }); @@ -625,7 +636,7 @@ registerAction2(class extends NotebookCellAction { NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_EDITABLE), order: CellToolbarOrder.SaveCell, - group: CELL_TITLE_GROUP_ID + group: CELL_TITLE_CELL_GROUP_ID }, icon: { id: 'codicon/check' }, keybinding: { @@ -659,7 +670,7 @@ registerAction2(class extends NotebookCellAction { id: MenuId.NotebookCellTitle, order: CellToolbarOrder.DeleteCell, when: NOTEBOOK_EDITOR_EDITABLE, - group: CELL_TITLE_GROUP_ID + group: CELL_TITLE_CELL_GROUP_ID }, keybinding: { primary: KeyCode.Delete, @@ -758,6 +769,11 @@ registerAction2(class extends NotebookCellAction { primary: KeyMod.CtrlCmd | KeyCode.KEY_C, weight: EDITOR_WIDGET_ACTION_WEIGHT }, + menu: { + id: MenuId.NotebookCellTitle, + when: NOTEBOOK_EDITOR_FOCUSED, + group: '1_copy', + } }); } @@ -780,6 +796,11 @@ registerAction2(class extends NotebookCellAction { primary: KeyMod.CtrlCmd | KeyCode.KEY_X, weight: EDITOR_WIDGET_ACTION_WEIGHT }, + menu: { + id: MenuId.NotebookCellTitle, + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE), + group: '1_copy', + } }); } @@ -864,6 +885,11 @@ registerAction2(class extends NotebookAction { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, weight: EDITOR_WIDGET_ACTION_WEIGHT }, + menu: { + id: MenuId.NotebookCellTitle, + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE), + group: '1_copy', + } }); } @@ -1176,7 +1202,7 @@ registerAction2(class extends NotebookCellAction { id: MenuId.NotebookCellTitle, when: ContextKeyExpr.and(NOTEBOOK_CELL_TYPE.isEqualTo('code'), NOTEBOOK_EDITOR_RUNNABLE), order: CellToolbarOrder.ClearCellOutput, - group: CELL_TITLE_GROUP_ID + group: CELL_TITLE_OUTPUT_GROUP_ID }, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey), NOTEBOOK_CELL_HAS_OUTPUTS), @@ -1339,7 +1365,11 @@ registerAction2(class extends NotebookCellAction { id: MenuId.NotebookCellTitle, when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE, InputFocusedContext), order: CellToolbarOrder.SplitCell, - group: CELL_TITLE_GROUP_ID + group: CELL_TITLE_CELL_GROUP_ID, + // alt: { + // id: JOIN_CELL_BELOW_COMMAND_ID, + // title: localize('notebookActions.joinCellBelow', "Join with Next Cell") + // } }, icon: { id: 'codicon/split-vertical' }, keybinding: { @@ -1392,6 +1422,11 @@ registerAction2(class extends NotebookCellAction { when: NOTEBOOK_EDITOR_FOCUSED, primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KEY_J, weight: KeybindingWeight.WorkbenchContrib + }, + menu: { + id: MenuId.NotebookCellTitle, + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE), + group: '2_edit', } }); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts b/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts index 94ce77aee8..c85516dbc5 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts @@ -166,8 +166,7 @@ export class KernelStatus extends Disposable implements IWorkbenchContribution { const activeEditor = getActiveNotebookEditor(this._editorService); - if (activeEditor && activeEditor.multipleKernelsAvailable) { - this.showKernelStatus(activeEditor.activeKernel); + if (activeEditor) { this._editorDisposable.add(activeEditor.onDidChangeKernel(() => { if (activeEditor.multipleKernelsAvailable) { this.showKernelStatus(activeEditor.activeKernel); @@ -175,6 +174,18 @@ export class KernelStatus extends Disposable implements IWorkbenchContribution { this.kernelInfoElement.clear(); } })); + + this._editorDisposable.add(activeEditor.onDidChangeAvailableKernels(() => { + if (activeEditor.multipleKernelsAvailable) { + this.showKernelStatus(activeEditor.activeKernel); + } else { + this.kernelInfoElement.clear(); + } + })); + } + + if (activeEditor && activeEditor.multipleKernelsAvailable) { + this.showKernelStatus(activeEditor.activeKernel); } else { this.kernelInfoElement.clear(); } diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index 38f355bb49..e226929a63 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -488,6 +488,9 @@ .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container { position: absolute; display: flex; + justify-content: center; + z-index: 30; /* over the focus outline on the editor */ + width: 100%; opacity: 0; transition: opacity 0.2s ease-in-out; cursor: auto; @@ -531,19 +534,8 @@ align-items: center; } -.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container .separator { - height: 1px; - flex-grow: 1; - align-self: center; -} - -.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container .action-item:first-child::after { - content: ' '; - display: block; - height: 1px; - width: 16px; - align-self: center; - margin: 0px 8px; +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container .action-item:first-child { + margin-right: 16px; } .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container span.codicon { @@ -699,7 +691,7 @@ .monaco-workbench .notebookOverlay > .cell-list-container .notebook-folding-indicator .codicon { visibility: visible; - padding: 8px 0 0 10px; + padding: 10px 0 0 10px; } /** Theming */ @@ -739,3 +731,11 @@ .monaco-workbench.vs-dark .monaco-workbench .notebookOverlay .cell.markdown table > tbody > tr > td { border-color: rgba(255, 255, 255, 0.18); } */ + +.monaco-action-bar .action-item.verticalSeparator { + width: 1px !important; + background-color: #bbb; + height: 16px !important; + margin: 5px 4px !important; + cursor: none; +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 2452ae4e2e..219873fea2 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -180,6 +180,7 @@ export interface INotebookEditor extends IEditor { isNotebookEditor: boolean; activeKernel: INotebookKernelInfo | INotebookKernelInfo2 | undefined; multipleKernelsAvailable: boolean; + readonly onDidChangeAvailableKernels: Event; readonly onDidChangeKernel: Event; isDisposed: boolean; @@ -240,10 +241,16 @@ export interface INotebookEditor extends IEditor { moveCellDown(cell: ICellViewModel): Promise; /** + * @deprecated Note that this method doesn't support batch operations, use #moveCellToIdx instead. * Move a cell above or below another cell */ moveCell(cell: ICellViewModel, relativeToCell: ICellViewModel, direction: 'above' | 'below'): Promise; + /** + * Move a cell to a specific position + */ + moveCellToIdx(cell: ICellViewModel, index: number): Promise; + /** * Focus the container of a cell (the monaco editor inside is not focused). */ diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index a328ad50e3..b21a6c97bb 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -75,6 +75,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private _overlayContainer!: HTMLElement; private _body!: HTMLElement; private _webview: BackLayerWebView | null = null; + private _webviewResolved: boolean = false; + private _webviewResolvePromise: Promise | null = null; private _webviewTransparentCover: HTMLElement | null = null; private _list: INotebookCellList | undefined; private _dndController: CellDragAndDropController | null = null; @@ -135,6 +137,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private _activeKernel: INotebookKernelInfo | INotebookKernelInfo2 | undefined = undefined; private readonly _onDidChangeKernel = this._register(new Emitter()); readonly onDidChangeKernel: Event = this._onDidChangeKernel.event; + private readonly _onDidChangeAvailableKernels = this._register(new Emitter()); + readonly onDidChangeAvailableKernels: Event = this._onDidChangeAvailableKernels.event; get activeKernel() { return this._activeKernel; @@ -150,7 +154,16 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } private _currentKernelTokenSource: CancellationTokenSource | undefined = undefined; - multipleKernelsAvailable: boolean = false; + private _multipleKernelsAvailable: boolean = false; + + get multipleKernelsAvailable() { + return this._multipleKernelsAvailable; + } + + set multipleKernelsAvailable(state: boolean) { + this._multipleKernelsAvailable = state; + this._onDidChangeAvailableKernels.fire(); + } private readonly _onDidChangeActiveEditor = this._register(new Emitter()); readonly onDidChangeActiveEditor: Event = this._onDidChangeActiveEditor.event; @@ -572,7 +585,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor // @deprecated if (provider && provider.kernel) { // it has a builtin kernel, don't automatically choose a kernel - this._loadKernelPreloads(provider.providerExtensionLocation, provider.kernel); + await this._loadKernelPreloads(provider.providerExtensionLocation, provider.kernel); tokenSource.dispose(); return; } @@ -591,7 +604,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor // the provider doesn't have a builtin kernel, choose a kernel this.activeKernel = availableKernels[0]; if (this.activeKernel) { - this._loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel); + await this._loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel); } tokenSource.dispose(); @@ -611,7 +624,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } if (this.activeKernel) { - this._loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel); + await this._loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel); await (this.activeKernel as INotebookKernelInfo2).resolve(this.viewModel!.uri, this.getId(), tokenSource.token); // {{SQL CARBON EDIT}} strict-null-checks } @@ -624,7 +637,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor if (kernelsFromSameExtension.length) { const preferedKernel = kernelsFromSameExtension.find(kernel => kernel.isPreferred) || kernelsFromSameExtension[0]; this.activeKernel = preferedKernel; - this._loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel); + await this._loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel); await preferedKernel.resolve(this.viewModel!.uri, this.getId(), tokenSource.token); tokenSource.dispose(); return; @@ -633,15 +646,16 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor // the provider doesn't have a builtin kernel, choose a kernel this.activeKernel = kernels[0]; if (this.activeKernel) { - this._loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel); + await this._loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel); await this.activeKernel.resolve(this.viewModel!.uri, this.getId(), tokenSource.token); } tokenSource.dispose(); } - private _loadKernelPreloads(extensionLocation: URI, kernel: INotebookKernelInfoDto) { - if (kernel.preloads) { + private async _loadKernelPreloads(extensionLocation: URI, kernel: INotebookKernelInfoDto) { + if (kernel.preloads && kernel.preloads.length) { + await this._resolveWebview(); this._webview?.updateKernelPreloads([extensionLocation], kernel.preloads.map(preload => URI.revive(preload))); } } @@ -656,34 +670,63 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._notebookExecuting?.set(notebookMetadata.runState === NotebookRunState.Running); } + private async _resolveWebview(): Promise { + if (!this.textModel) { + return null; + } + + if (this._webviewResolvePromise) { + return this._webviewResolvePromise; + } + + if (!this._webview) { + this._webview = this.instantiationService.createInstance(BackLayerWebView, this, this.getId(), this.textModel!.uri); + // attach the webview container to the DOM tree first + this._list?.rowsContainer.insertAdjacentElement('afterbegin', this._webview.element); + } + + this._webviewResolvePromise = new Promise(async resolve => { + await this._webview!.createWebview(); + this._webview!.webview!.onDidBlur(() => { + this._outputFocus?.set(false); + this.updateEditorFocus(); + + if (this._overlayContainer.contains(document.activeElement)) { + this._webiewFocused = false; + } + }); + this._webview!.webview!.onDidFocus(() => { + this._outputFocus?.set(true); + this.updateEditorFocus(); + this._onDidFocusEmitter.fire(); + + if (this._overlayContainer.contains(document.activeElement)) { + this._webiewFocused = true; + } + }); + + this._localStore.add(this._webview!.onMessage(({ message, forRenderer }) => { + if (this.viewModel) { + this.notebookService.onDidReceiveMessage(this.viewModel.viewType, this.getId(), forRenderer, message); + } + })); + + if (this.viewModel && this.viewModel!.renderers.size) { + this._webview?.updateRendererPreloads(this.viewModel!.renderers); + } + + this._webviewResolved = true; + + resolve(this._webview!); + }); + + return this._webviewResolvePromise; + } + private async _createWebview(id: string, resource: URI): Promise { this._webview = this.instantiationService.createInstance(BackLayerWebView, this, id, resource); // attach the webview container to the DOM tree first this._list?.rowsContainer.insertAdjacentElement('afterbegin', this._webview.element); - await this._webview.createWebview(); - this._webview.webview.onDidBlur(() => { - this._outputFocus?.set(false); - this.updateEditorFocus(); - - if (this._overlayContainer.contains(document.activeElement)) { - this._webiewFocused = false; - } - }); - this._webview.webview.onDidFocus(() => { - this._outputFocus?.set(true); - this.updateEditorFocus(); - this._onDidFocusEmitter.fire(); - - if (this._overlayContainer.contains(document.activeElement)) { - this._webiewFocused = true; - } - }); - - this._localStore.add(this._webview.onMessage(({ message, forRenderer }) => { - if (this.viewModel) { - this.notebookService.onDidReceiveMessage(this.viewModel.viewType, this.getId(), forRenderer, message); - } - })); } private async _attachModel(textModel: NotebookTextModel, viewState: INotebookEditorViewState | undefined) { @@ -717,10 +760,17 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } } - this._webview?.updateRendererPreloads(this.viewModel.renderers); + if (this.viewModel.renderers.size) { + await this._resolveWebview(); + this._webview?.updateRendererPreloads(this.viewModel.renderers); + } this._localStore.add(this._list!.onWillScroll(e => { - this._webview!.updateViewScrollTop(-e.scrollTop, []); + if (!this._webviewResolved) { + return; + } + + this._webview?.updateViewScrollTop(-e.scrollTop, []); this._webviewTransparentCover!.style.top = `${e.scrollTop}px`; })); @@ -732,6 +782,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor const scrollTop = this._list?.scrollTop || 0; const scrollHeight = this._list?.scrollHeight || 0; + + if (!this._webviewResolved) { + return; + } + this._webview!.element.style.height = `${scrollHeight}px`; if (this._webview?.insetMapping) { @@ -1128,6 +1183,15 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return this._moveCellToIndex(originalIdx, newIdx); } + async moveCellToIdx(cell: ICellViewModel, index: number): Promise { + if (!this._notebookViewModel!.metadata.editable) { + return null; + } + + const originalIdx = this._notebookViewModel!.getCellIndex(cell); + return this._moveCellToIndex(originalIdx, index); + } + private async _moveCellToIndex(index: number, newIdx: number): Promise { if (index === newIdx) { return null; @@ -1356,6 +1420,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return; } + await this._resolveWebview(); + let preloads = this._notebookViewModel!.renderers; if (!this._webview!.insetMapping.has(output)) { @@ -1370,7 +1436,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } removeInset(output: IProcessedOutput) { - if (!this._webview) { + if (!this._webview || !this._webviewResolved) { return; } @@ -1378,7 +1444,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } hideInset(output: IProcessedOutput) { - if (!this._webview) { + if (!this._webview || !this._webviewResolved) { return; } @@ -1390,10 +1456,14 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } postMessage(forRendererId: string | undefined, message: any) { + if (!this._webview || !this._webviewResolved) { + return; + } + if (forRendererId === undefined) { - this._webview?.webview.postMessage(message); + this._webview.webview?.postMessage(message); } else { - this._webview?.postRendererMessage(forRendererId, message); + this._webview.postRendererMessage(forRendererId, message); } } @@ -1600,13 +1670,13 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.notebookOverlay .cell-statusbar-container { border-top: solid 1px ${editorBackgroundColor}; }`); collector.addRule(`.notebookOverlay .monaco-list-row > .monaco-toolbar { background-color: ${editorBackgroundColor}; }`); collector.addRule(`.notebookOverlay .monaco-list-row.cell-drag-image { background-color: ${editorBackgroundColor}; }`); + collector.addRule(`.notebookOverlay .cell-bottom-toolbar-container .action-item { background-color: ${editorBackgroundColor} }`); } const cellToolbarSeperator = theme.getColor(CELL_TOOLBAR_SEPERATOR); if (cellToolbarSeperator) { - collector.addRule(`.notebookOverlay .cell-bottom-toolbar-container .separator { background-color: ${cellToolbarSeperator} }`); - collector.addRule(`.notebookOverlay .cell-bottom-toolbar-container .action-item:first-child::after { background-color: ${cellToolbarSeperator} }`); collector.addRule(`.notebookOverlay .monaco-list-row > .monaco-toolbar { border: solid 1px ${cellToolbarSeperator}; }`); + collector.addRule(`.notebookOverlay .cell-bottom-toolbar-container .action-item { border: solid 1px ${cellToolbarSeperator} }`); } const focusedCellBackgroundColor = theme.getColor(focusedCellBackground); @@ -1643,14 +1713,10 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.notebookOverlay .monaco-list-row.cell-editor-focus .cell-editor-part:before { outline: solid 1px ${focusedEditorBorderColorColor}; }`); } - const editorBorderColor = theme.getColor(notebookCellBorder); - if (editorBorderColor) { - collector.addRule(`.notebookOverlay .monaco-list-row .cell-editor-part:before { outline: solid 1px ${editorBorderColor}; }`); - } - - const headingBorderColor = theme.getColor(notebookCellBorder); - if (headingBorderColor) { - collector.addRule(`.notebookOverlay .cell.markdown h1 { border-color: ${headingBorderColor}; }`); + const cellBorderColor = theme.getColor(notebookCellBorder); + if (cellBorderColor) { + collector.addRule(`.notebookOverlay .cell.markdown h1 { border-color: ${cellBorderColor}; }`); + collector.addRule(`.notebookOverlay .monaco-list-row .cell-editor-part:before { outline: solid 1px ${cellBorderColor}; }`); } const cellStatusSuccessIcon = theme.getColor(cellStatusIconSuccess); @@ -1701,10 +1767,8 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > div.cell.code { margin-left: ${CODE_CELL_LEFT_MARGIN}px; }`); collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row { padding-top: ${EDITOR_TOP_MARGIN}px; }`); collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row { padding-bottom: ${CELL_BOTTOM_MARGIN}px; }`); - collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row .cell-bottom-toolbar-container { margin-top: ${CELL_BOTTOM_MARGIN}px; }`); collector.addRule(`.notebookOverlay .output { margin: 0px ${CELL_MARGIN}px 0px ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`); collector.addRule(`.notebookOverlay .output { width: calc(100% - ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER + (CELL_MARGIN * 2)}px); }`); - collector.addRule(`.notebookOverlay .cell-bottom-toolbar-container { width: calc(100% - ${CELL_MARGIN * 2 + CELL_RUN_GUTTER}px); margin: 0px ${CELL_MARGIN * 2}px 0px ${CELL_MARGIN + CELL_RUN_GUTTER}px; }`); collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > div.cell.markdown { padding-left: ${CELL_RUN_GUTTER}px; }`); collector.addRule(`.notebookOverlay .cell .run-button-container { width: ${CELL_RUN_GUTTER}px; }`); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 53319f6c66..f1aa6a888c 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -219,7 +219,7 @@ export interface INotebookWebviewMessage { let version = 0; export class BackLayerWebView extends Disposable { element: HTMLElement; - webview!: WebviewElement; + webview: WebviewElement | undefined = undefined; insetMapping: Map = new Map(); hiddenInsetMapping: Set = new Set(); reversedInsetMapping: Map = new Map(); @@ -714,7 +714,7 @@ ${loaderJs} return; } - this.webview.focus(); + this.webview?.focus(); } focusOutput(cellId: string) { @@ -722,7 +722,7 @@ ${loaderJs} return; } - this.webview.focus(); + this.webview?.focus(); setTimeout(() => { // Need this, or focus decoration is not shown. No clue. this._sendMessageToWebview({ type: 'focus-output', @@ -814,6 +814,10 @@ ${loaderJs} } private _updatePreloads(resources: IPreloadResource[], source: 'renderer' | 'kernel') { + if (!this.webview) { + return; + } + const mixedResourceRoots = [...(this.localResourceRootsCache || []), ...this.rendererRootsCache, ...this.kernelRootsCache]; this.webview.localResourcesRoot = mixedResourceRoots; @@ -830,7 +834,7 @@ ${loaderJs} return; } - this.webview.postMessage(message); + this.webview?.postMessage(message); } clearPreloadsCache() { @@ -839,7 +843,7 @@ ${loaderJs} dispose() { this._disposed = true; - this.webview.dispose(); + this.webview?.dispose(); super.dispose(); } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellActionView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellActionView.ts new file mode 100644 index 0000000000..f3cbb4e71b --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellActionView.ts @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as DOM from 'vs/base/browser/dom'; +import { Action, IAction } from 'vs/base/common/actions'; +import { BaseActionViewItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; + +export class VerticalSeparator extends Action { + static readonly ID = 'vs.actions.verticalSeparator'; + + constructor( + label?: string + ) { + super(VerticalSeparator.ID, label, label ? 'verticalSeparator text' : 'verticalSeparator'); + this.checked = false; + this.enabled = false; + } +} + +export class VerticalSeparatorViewItem extends BaseActionViewItem { + render(container: HTMLElement) { + DOM.addClass(container, 'verticalSeparator'); + // const iconContainer = DOM.append(container, $('.verticalSeparator')); + // DOM.addClasses(iconContainer, 'codicon', 'codicon-chrome-minimize'); + } +} + +export function createAndFillInActionBarActionsWithVerticalSeparators(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, isPrimaryGroup?: (group: string) => boolean): IDisposable { + const groups = menu.getActions(options); + // Action bars handle alternative actions on their own so the alternative actions should be ignored + fillInActions(groups, target, false, isPrimaryGroup); + return asDisposable(groups); +} + +function fillInActions(groups: ReadonlyArray<[string, ReadonlyArray]>, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, useAlternativeActions: boolean, isPrimaryGroup: (group: string) => boolean = group => group === 'navigation'): void { + for (let tuple of groups) { + let [group, actions] = tuple; + if (useAlternativeActions) { + actions = actions.map(a => (a instanceof MenuItemAction) && !!a.alt ? a.alt : a); + } + + if (isPrimaryGroup(group)) { + const to = Array.isArray(target) ? target : target.primary; + + if (to.length > 0) { + to.push(new VerticalSeparator()); + } + + to.push(...actions); + } else { + const to = Array.isArray(target) ? target : target.secondary; + + if (to.length > 0) { + to.push(new Separator()); + } + + to.push(...actions); + } + } +} + +function asDisposable(groups: ReadonlyArray<[string, ReadonlyArray]>): IDisposable { + const disposables = new DisposableStore(); + for (const [, actions] of groups) { + for (const action of actions) { + disposables.add(action); + } + } + return disposables; +} diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellMenus.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellMenus.ts index 9adad43c73..38e9cdc831 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellMenus.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellMenus.ts @@ -3,16 +3,12 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAction } from 'vs/base/common/actions'; -import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; export class CellMenus { constructor( @IMenuService private readonly menuService: IMenuService, - @IContextMenuService private readonly contextMenuService: IContextMenuService ) { } getCellTitleMenu(contextKeyService: IContextKeyService): IMenu { @@ -26,11 +22,6 @@ export class CellMenus { private getMenu(menuId: MenuId, contextKeyService: IContextKeyService): IMenu { const menu = this.menuService.createMenu(menuId, contextKeyService); - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - - createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => /^inline/.test(g)); return menu; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index c6ba378683..10adb50a30 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -36,17 +36,18 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { BOTTOM_CELL_TOOLBAR_HEIGHT, EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING, CELL_BOTTOM_MARGIN } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CancelCellAction, ChangeCellLanguageAction, ExecuteCellAction, INotebookCellActionContext, CELL_TITLE_GROUP_ID } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; -import { BaseCellRenderTemplate, CellEditState, CodeCellRenderTemplate, ICellViewModel, INotebookCellList, INotebookEditor, MarkdownCellRenderTemplate, isCodeCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CancelCellAction, ChangeCellLanguageAction, ExecuteCellAction, INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; +import { BaseCellRenderTemplate, CellEditState, CodeCellRenderTemplate, ICellViewModel, INotebookCellList, INotebookEditor, isCodeCellRenderTemplate, MarkdownCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys'; import { CellMenus } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellMenus'; import { CodeCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/codeCell'; -import { StatefullMarkdownCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/markdownCell'; +import { StatefulMarkdownCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/markdownCell'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { CellKind, NotebookCellRunState, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys'; +import { CellKind, NotebookCellMetadata, NotebookCellRunState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { VerticalSeparator, createAndFillInActionBarActionsWithVerticalSeparators, VerticalSeparatorViewItem } from './cellActionView'; const $ = DOM.$; @@ -204,9 +205,6 @@ abstract class AbstractCellRenderer { } }); - toolbar.getContainer().style.height = `${BOTTOM_CELL_TOOLBAR_HEIGHT}px`; - container.style.height = `${BOTTOM_CELL_TOOLBAR_HEIGHT}px`; - const cellMenu = this.instantiationService.createInstance(CellMenus); const menu = disposables.add(cellMenu.getCellInsertionMenu(contextKeyService)); @@ -220,25 +218,28 @@ abstract class AbstractCellRenderer { templateData.betweenCellToolbar.context = context; const container = templateData.bottomCellContainer; - if (element instanceof CodeCellViewModel) { + const bottomToolbarOffset = element.layoutInfo.bottomToolbarOffset; + container.style.top = `${bottomToolbarOffset}px`; + + templateData.elementDisposables.add(element.onDidChangeLayout(() => { const bottomToolbarOffset = element.layoutInfo.bottomToolbarOffset; container.style.top = `${bottomToolbarOffset}px`; - - templateData.elementDisposables.add(element.onDidChangeLayout(() => { - const bottomToolbarOffset = element.layoutInfo.bottomToolbarOffset; - container.style.top = `${bottomToolbarOffset}px`; - })); - } + })); } protected createToolbar(container: HTMLElement): ToolBar { const toolbar = new ToolBar(container, this.contextMenuService, { + getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), actionViewItemProvider: action => { if (action instanceof MenuItemAction) { const item = new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); return item; } + if (action.id === VerticalSeparator.ID) { + return new VerticalSeparatorViewItem(undefined, action); + } + return undefined; } }); @@ -249,16 +250,11 @@ abstract class AbstractCellRenderer { private getCellToolbarActions(menu: IMenu): { primary: IAction[], secondary: IAction[] } { const primary: IAction[] = []; const secondary: IAction[] = []; - const actions = menu.getActions({ shouldForwardArgs: true }); - for (let [id, menuActions] of actions) { - if (id === CELL_TITLE_GROUP_ID) { - primary.push(...menuActions); - } else { - secondary.push(...menuActions); - } - } + const result = { primary, secondary }; - return { primary, secondary }; + createAndFillInActionBarActionsWithVerticalSeparators(menu, { shouldForwardArgs: true }, result, g => /^inline/.test(g)); + + return result; } protected setupCellToolbarActions(templateData: BaseCellRenderTemplate, disposables: DisposableStore): void { @@ -351,9 +347,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR const foldingIndicator = DOM.append(focusIndicator, DOM.$('.notebook-folding-indicator')); const bottomCellContainer = DOM.append(container, $('.cell-bottom-toolbar-container')); - DOM.append(bottomCellContainer, $('.separator')); const betweenCellToolbar = disposables.add(this.createBetweenCellToolbar(bottomCellContainer, disposables, contextKeyService)); - DOM.append(bottomCellContainer, $('.separator')); const statusBar = this.instantiationService.createInstance(CellEditorStatusBar, editorPart); const titleMenu = disposables.add(this.cellMenus.getCellTitleMenu(contextKeyService)); @@ -439,7 +433,8 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR this.setBetweenCellToolbarContext(templateData, element, toolbarContext); - const markdownCell = this.instantiationService.createInstance(StatefullMarkdownCell, this.notebookEditor, element, templateData, this.editorOptions.value, this.renderedEditors); + const scopedInstaService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, templateData.contextKeyService])); + const markdownCell = scopedInstaService.createInstance(StatefulMarkdownCell, this.notebookEditor, element, templateData, this.editorOptions.value, this.renderedEditors); elementDisposables.add(this.editorOptions.onDidChange(newValue => markdownCell.updateEditorOptions(newValue))); elementDisposables.add(markdownCell); @@ -611,6 +606,18 @@ export class CellDragAndDropController extends Disposable { private onCellDrop(event: CellDragEvent): void { const draggedCell = this.currentDraggedCell!; + let draggedCells: ICellViewModel[] = [draggedCell]; + + if (draggedCell.cellKind === CellKind.Markdown) { + const currCellIndex = this.notebookEditor.viewModel!.getCellIndex(draggedCell); + const nextVisibleCellIndex = this.notebookEditor.viewModel!.getNextVisibleCellIndex(currCellIndex); + + if (nextVisibleCellIndex > currCellIndex + 1) { + // folding ;) + draggedCells = this.notebookEditor.viewModel!.viewCells.slice(currCellIndex, nextVisibleCellIndex); + } + } + this.dragCleanup(); const isCopy = (event.browserEvent.ctrlKey && !platform.isMacintosh) || (event.browserEvent.altKey && platform.isMacintosh); @@ -625,9 +632,9 @@ export class CellDragAndDropController extends Disposable { } if (isCopy) { - this.copyCell(draggedCell, event.draggedOverCell, dropDirection); + this.copyCells(draggedCells, event.draggedOverCell, dropDirection); } else { - this.moveCell(draggedCell, event.draggedOverCell, dropDirection); + this.moveCells(draggedCells, event.draggedOverCell, dropDirection); } } @@ -673,16 +680,37 @@ export class CellDragAndDropController extends Disposable { })); } - private async moveCell(draggedCell: ICellViewModel, ontoCell: ICellViewModel, direction: 'above' | 'below') { - await this.notebookEditor.moveCell(draggedCell, ontoCell, direction); + private async moveCells(draggedCells: ICellViewModel[], ontoCell: ICellViewModel, direction: 'above' | 'below') { + const relativeToIndex = this.notebookEditor!.viewModel!.getCellIndex(ontoCell); + const newIdx = direction === 'above' ? relativeToIndex : relativeToIndex + 1; + + this.notebookEditor.textModel!.pushStackElement('Move Cells'); + for (let i = draggedCells.length - 1; i >= 0; i--) { + await this.notebookEditor.moveCellToIdx(draggedCells[i], newIdx); + } + + this.notebookEditor.textModel!.pushStackElement('Move Cells'); } - private copyCell(draggedCell: ICellViewModel, ontoCell: ICellViewModel, direction: 'above' | 'below') { - const editState = draggedCell.editState; - const newCell = this.notebookEditor.insertNotebookCell(ontoCell, draggedCell.cellKind, direction, draggedCell.getText()); - if (newCell) { - this.notebookEditor.focusNotebookCell(newCell, editState === CellEditState.Editing ? 'editor' : 'container'); + private copyCells(draggedCells: ICellViewModel[], ontoCell: ICellViewModel, direction: 'above' | 'below') { + this.notebookEditor.textModel!.pushStackElement('Copy Cells'); + let firstNewCell: ICellViewModel | undefined = undefined; + let firstNewCellState: CellEditState = CellEditState.Preview; + for (let i = 0; i < draggedCells.length; i++) { + const draggedCell = draggedCells[i]; + const newCell = this.notebookEditor.insertNotebookCell(ontoCell, draggedCell.cellKind, direction, draggedCell.getText()); + + if (newCell && !firstNewCell) { + firstNewCell = newCell; + firstNewCellState = draggedCell.editState; + } } + + if (firstNewCell) { + this.notebookEditor.focusNotebookCell(firstNewCell, firstNewCellState === CellEditState.Editing ? 'editor' : 'container'); + } + + this.notebookEditor.textModel!.pushStackElement('Copy Cells'); } } @@ -912,11 +940,8 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende const focusSinkElement = DOM.append(container, $('.cell-editor-focus-sink')); focusSinkElement.setAttribute('tabindex', '0'); const bottomCellContainer = DOM.append(container, $('.cell-bottom-toolbar-container')); - DOM.append(bottomCellContainer, $('.separator')); - const betweenCellToolbar = this.createBetweenCellToolbar(bottomCellContainer, disposables, contextKeyService); - DOM.append(bottomCellContainer, $('.separator')); - const focusIndicatorBottom = DOM.append(container, $('.cell-focus-indicator.cell-focus-indicator-bottom')); + const betweenCellToolbar = this.createBetweenCellToolbar(bottomCellContainer, disposables, contextKeyService); const titleMenu = disposables.add(this.cellMenus.getCellTitleMenu(contextKeyService)); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts index 485d4d2b82..750329a39d 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts @@ -21,7 +21,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver'; -export class StatefullMarkdownCell extends Disposable { +export class StatefulMarkdownCell extends Disposable { private editor: CodeEditorWidget | null = null; private markdownContainer: HTMLElement; @@ -95,7 +95,7 @@ export class StatefullMarkdownCell extends Disposable { this._register(viewCell.onDidChangeLayout((e) => { const layoutInfo = this.editor?.getLayoutInfo(); - if (e.outerWidth && layoutInfo && layoutInfo.width !== viewCell.layoutInfo.editorWidth) { + if (e.outerWidth && this.viewCell.editState === CellEditState.Editing && layoutInfo && layoutInfo.width !== viewCell.layoutInfo.editorWidth) { this.onCellEditorWidthChange(); } else if (e.totalHeight || e.outerWidth) { this.relayoutCell(); diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts index 1557de97cf..464d7bdac3 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -8,7 +8,7 @@ import * as UUID from 'vs/base/common/uuid'; import * as editorCommon from 'vs/editor/common/editorCommon'; import * as model from 'vs/editor/common/model'; import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; -import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_MARGIN, CELL_RUN_GUTTER, CELL_STATUSBAR_HEIGHT, EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN } from 'vs/workbench/contrib/notebook/browser/constants'; +import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_MARGIN, CELL_RUN_GUTTER, CELL_STATUSBAR_HEIGHT, EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN, BOTTOM_CELL_TOOLBAR_OFFSET } from 'vs/workbench/contrib/notebook/browser/constants'; import { CellEditState, CellFindMatch, CodeCellLayoutChangeEvent, CodeCellLayoutInfo, ICellViewModel, NotebookLayoutInfo, CodeCellLayoutState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { CellKind, NotebookCellOutputsSplice, INotebookSearchOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -122,7 +122,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod const indicatorHeight = editorHeight + CELL_STATUSBAR_HEIGHT + outputTotalHeight; const outputContainerOffset = EDITOR_TOOLBAR_HEIGHT + EDITOR_TOP_MARGIN + editorHeight + CELL_STATUSBAR_HEIGHT; - const bottomToolbarOffset = totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT; + const bottomToolbarOffset = totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT - BOTTOM_CELL_TOOLBAR_OFFSET; const editorWidth = state.outerWidth !== undefined ? this.computeEditorWidth(state.outerWidth) : this._layoutInfo?.editorWidth; this._layoutInfo = { diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts index 5814924469..0e5e7700c0 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts @@ -8,7 +8,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import * as UUID from 'vs/base/common/uuid'; import * as editorCommon from 'vs/editor/common/editorCommon'; import * as model from 'vs/editor/common/model'; -import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_MARGIN, CELL_STATUSBAR_HEIGHT, EDITOR_TOP_MARGIN, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN } from 'vs/workbench/contrib/notebook/browser/constants'; +import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_MARGIN, CELL_STATUSBAR_HEIGHT, EDITOR_TOP_MARGIN, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN, BOTTOM_CELL_TOOLBAR_OFFSET } from 'vs/workbench/contrib/notebook/browser/constants'; import { CellFindMatch, ICellViewModel, MarkdownCellLayoutChangeEvent, MarkdownCellLayoutInfo, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { MarkdownRenderer } from 'vs/workbench/contrib/notebook/browser/view/renderers/mdRenderer'; import { BaseCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel'; @@ -93,13 +93,14 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie layoutChange(state: MarkdownCellLayoutChangeEvent) { // recompute const editorWidth = state.outerWidth !== undefined ? this.computeEditorWidth(state.outerWidth) : this._layoutInfo.editorWidth; + const totalHeight = state.totalHeight === undefined ? this._layoutInfo.totalHeight : state.totalHeight; this._layoutInfo = { fontInfo: state.font || this._layoutInfo.fontInfo, editorWidth, editorHeight: this._editorHeight, - bottomToolbarOffset: BOTTOM_CELL_TOOLBAR_HEIGHT, - totalHeight: state.totalHeight === undefined ? this._layoutInfo.totalHeight : state.totalHeight + bottomToolbarOffset: totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT - BOTTOM_CELL_TOOLBAR_OFFSET, + totalHeight }; this._onDidChangeLayout.fire(state); @@ -115,6 +116,7 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie totalHeight: totalHeight, editorHeight: this._editorHeight }; + this.layoutChange({}); } } diff --git a/src/vs/workbench/contrib/notebook/common/model/cellEdit.ts b/src/vs/workbench/contrib/notebook/common/model/cellEdit.ts index 6adc6bc981..cbb9c2dfe3 100644 --- a/src/vs/workbench/contrib/notebook/common/model/cellEdit.ts +++ b/src/vs/workbench/contrib/notebook/common/model/cellEdit.ts @@ -31,7 +31,7 @@ export class InsertCellEdit implements IResourceUndoRedoElement { ) { } - undo(): void | Promise { + undo(): void { if (!this.editingDelegate.deleteCell) { throw new Error('Notebook Delete Cell not implemented for Undo/Redo'); } @@ -41,7 +41,7 @@ export class InsertCellEdit implements IResourceUndoRedoElement { this.editingDelegate.emitSelections(this.beforedSelections); } } - redo(): void | Promise { + redo(): void { if (!this.editingDelegate.insertCell) { throw new Error('Notebook Insert Cell not implemented for Undo/Redo'); } @@ -70,7 +70,7 @@ export class DeleteCellEdit implements IResourceUndoRedoElement { // this._rawCell.source = [cell.getText()]; } - undo(): void | Promise { + undo(): void { if (!this.editingDelegate.insertCell) { throw new Error('Notebook Insert Cell not implemented for Undo/Redo'); } @@ -81,7 +81,7 @@ export class DeleteCellEdit implements IResourceUndoRedoElement { } } - redo(): void | Promise { + redo(): void { if (!this.editingDelegate.deleteCell) { throw new Error('Notebook Delete Cell not implemented for Undo/Redo'); } @@ -95,7 +95,7 @@ export class DeleteCellEdit implements IResourceUndoRedoElement { export class MoveCellEdit implements IResourceUndoRedoElement { type: UndoRedoElementType.Resource = UndoRedoElementType.Resource; - label: string = 'Delete Cell'; + label: string = 'Move Cell'; constructor( public resource: URI, @@ -107,7 +107,7 @@ export class MoveCellEdit implements IResourceUndoRedoElement { ) { } - undo(): void | Promise { + undo(): void { if (!this.editingDelegate.moveCell) { throw new Error('Notebook Move Cell not implemented for Undo/Redo'); } @@ -118,7 +118,7 @@ export class MoveCellEdit implements IResourceUndoRedoElement { } } - redo(): void | Promise { + redo(): void { if (!this.editingDelegate.moveCell) { throw new Error('Notebook Move Cell not implemented for Undo/Redo'); } @@ -142,7 +142,7 @@ export class SpliceCellsEdit implements IResourceUndoRedoElement { ) { } - undo(): void | Promise { + undo(): void { if (!this.editingDelegate.deleteCell || !this.editingDelegate.insertCell) { throw new Error('Notebook Insert/Delete Cell not implemented for Undo/Redo'); } @@ -162,7 +162,7 @@ export class SpliceCellsEdit implements IResourceUndoRedoElement { } } - redo(): void | Promise { + redo(): void { if (!this.editingDelegate.deleteCell || !this.editingDelegate.insertCell) { throw new Error('Notebook Insert/Delete Cell not implemented for Undo/Redo'); } diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index 93ae922efa..0f6a5d16e4 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { INotebookTextModel, NotebookCellOutputsSplice, NotebookCellTextModelSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, ICellInsertEdit, NotebookCellsChangedEvent, CellKind, IProcessedOutput, notebookDocumentMetadataDefaults, diff, ICellDeleteEdit, NotebookCellsChangeType, ICellDto2, IMainCellDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ITextSnapshot } from 'vs/editor/common/model'; -import { IUndoRedoService, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo'; +import { IUndoRedoService, UndoRedoElementType, IUndoRedoElement, IResourceUndoRedoElement } from 'vs/platform/undoRedo/common/undoRedo'; import { InsertCellEdit, DeleteCellEdit, MoveCellEdit, SpliceCellsEdit } from 'vs/workbench/contrib/notebook/common/model/cellEdit'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -66,6 +66,53 @@ export class NotebookTextModelSnapshot implements ITextSnapshot { } +class StackOperation implements IResourceUndoRedoElement { + type: UndoRedoElementType.Resource; + + private _operations: IUndoRedoElement[] = []; + + constructor(readonly resource: URI, readonly label: string) { + this.type = UndoRedoElementType.Resource; + } + + pushEditOperation(element: IUndoRedoElement) { + this._operations.push(element); + } + + undo(): void { + this._operations.reverse().forEach(o => o.undo()); + } + redo(): void | Promise { + this._operations.forEach(o => o.redo()); + } +} + +export class NotebookOperationManager { + private _pendingStackOperation: StackOperation | null = null; + constructor(private _undoService: IUndoRedoService, private _resource: URI) { + + } + + pushStackElement(label: string) { + if (this._pendingStackOperation) { + this._undoService.pushElement(this._pendingStackOperation); + this._pendingStackOperation = null; + return; + } + + this._pendingStackOperation = new StackOperation(this._resource, label); + } + + pushEditOperation(element: IUndoRedoElement) { + if (this._pendingStackOperation) { + this._pendingStackOperation.pushEditOperation(element); + return; + } + + this._undoService.pushElement(element); + } +} + export class NotebookTextModel extends Disposable implements INotebookTextModel { private _cellhandlePool: number = 0; @@ -112,6 +159,8 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel protected readonly _onDidChangeDirty = this._register(new Emitter()); readonly onDidChangeDirty = this._onDidChangeDirty.event; + private _operationManager: NotebookOperationManager; + constructor( public handle: number, public viewType: string, @@ -122,6 +171,8 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel ) { super(); this.cells = []; + + this._operationManager = new NotebookOperationManager(this._undoService, uri); } get isDirty() { @@ -173,6 +224,10 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel this._increaseVersionId(); } + pushStackElement(label: string) { + this._operationManager.pushStackElement(label); + } + $applyEdit(modelVersionId: number, rawEdits: ICellEditOperation[], synchronous: boolean): boolean { if (modelVersionId !== this._versionId) { return false; @@ -255,7 +310,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel return [diff[0], deletedCells, diff[2]] as [number, NotebookCellTextModel[], NotebookCellTextModel[]]; }); - this._undoService.pushElement(new SpliceCellsEdit(this.uri, undoDiff, { + this._operationManager.pushEditOperation(new SpliceCellsEdit(this.uri, undoDiff, { insertCell: this._insertCellDelegate.bind(this), deleteCell: this._deleteCellDelegate.bind(this), emitSelections: this._emitSelectionsDelegate.bind(this) @@ -266,7 +321,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel } $handleEdit(label: string | undefined, undo: () => void, redo: () => void): void { - this._undoService.pushElement({ + this._operationManager.pushEditOperation({ type: UndoRedoElementType.Resource, resource: this.uri, label: label ?? nls.localize('defaultEditLabel', "Edit"), @@ -502,7 +557,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel const cell = this.createCellTextModel(source, language, type, [], metadata); if (pushUndoStop) { - this._undoService.pushElement(new InsertCellEdit(this.uri, index, cell, { + this._operationManager.pushEditOperation(new InsertCellEdit(this.uri, index, cell, { insertCell: this._insertCellDelegate.bind(this), deleteCell: this._deleteCellDelegate.bind(this), emitSelections: this._emitSelectionsDelegate.bind(this) @@ -522,7 +577,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel insertCell2(index: number, cell: NotebookCellTextModel, synchronous: boolean, pushUndoStop: boolean): void { if (pushUndoStop) { - this._undoService.pushElement(new InsertCellEdit(this.uri, index, cell, { + this._operationManager.pushEditOperation(new InsertCellEdit(this.uri, index, cell, { insertCell: this._insertCellDelegate.bind(this), deleteCell: this._deleteCellDelegate.bind(this), emitSelections: this._emitSelectionsDelegate.bind(this) @@ -536,7 +591,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel deleteCell2(index: number, synchronous: boolean, pushUndoStop: boolean, beforeSelections: number[] | undefined, endSelections: number[] | undefined) { const cell = this.cells[index]; if (pushUndoStop) { - this._undoService.pushElement(new DeleteCellEdit(this.uri, index, cell, { + this._operationManager.pushEditOperation(new DeleteCellEdit(this.uri, index, cell, { insertCell: this._insertCellDelegate.bind(this), deleteCell: this._deleteCellDelegate.bind(this), emitSelections: this._emitSelectionsDelegate.bind(this) @@ -553,7 +608,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel moveCellToIdx2(index: number, newIdx: number, synchronous: boolean, pushedToUndoStack: boolean, beforeSelections: number[] | undefined, endSelections: number[] | undefined): boolean { const cell = this.cells[index]; if (pushedToUndoStack) { - this._undoService.pushElement(new MoveCellEdit(this.uri, index, newIdx, { + this._operationManager.pushEditOperation(new MoveCellEdit(this.uri, index, newIdx, { moveCell: (fromIndex: number, toIndex: number, beforeSelections: number[] | undefined, endSelections: number[] | undefined) => { this.moveCellToIdx2(fromIndex, toIndex, true, false, beforeSelections, endSelections); }, diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index 06ecdd65f4..c8727e9ec6 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -63,6 +63,8 @@ export class TestNotebookEditor implements INotebookEditor { ) { } multipleKernelsAvailable: boolean = false; + onDidChangeAvailableKernels: Event = new Emitter().event; + uri?: URI | undefined; textModel?: NotebookTextModel | undefined; @@ -158,6 +160,10 @@ export class TestNotebookEditor implements INotebookEditor { throw new Error('Method not implemented.'); } + moveCellToIdx(cell: ICellViewModel, index: number): Promise { + throw new Error('Method not implemented.'); + } + moveCell(cell: ICellViewModel, relativeToCell: ICellViewModel, direction: 'above' | 'below'): Promise { throw new Error('Method not implemented.'); } diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index b733a19a0e..c6ae6cdece 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -883,7 +883,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer resolve(undefined); } } else { - resolve(this.executeTask(task, resolver)); + resolve(this.executeTask(task, resolver, runSource)); } }).then((value) => { if (runSource === TaskRunSource.User) { @@ -1452,7 +1452,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }; } - private executeTask(task: Task, resolver: ITaskResolver): Promise { + private executeTask(task: Task, resolver: ITaskResolver, runSource?: TaskRunSource): Promise { enum SaveBeforeRunConfigOptions { Always = 'always', Never = 'never', @@ -1464,7 +1464,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const execTask = async (task: Task, resolver: ITaskResolver): Promise => { return ProblemMatcherRegistry.onReady().then(() => { let executeResult = this.getTaskSystem().run(task, resolver); - return this.handleExecuteResult(executeResult); + return this.handleExecuteResult(executeResult, runSource); }); }; @@ -1501,7 +1501,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } - private async handleExecuteResult(executeResult: ITaskExecuteResult): Promise { + private async handleExecuteResult(executeResult: ITaskExecuteResult, runSource?: TaskRunSource): Promise { if (executeResult.task.taskLoadMessages && executeResult.task.taskLoadMessages.length > 0) { executeResult.task.taskLoadMessages.forEach(loadMessage => { this._outputChannel.append(loadMessage + '\n'); @@ -1509,7 +1509,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this.showOutput(); } - await this.setRecentlyUsedTask(executeResult.task); + if (runSource === TaskRunSource.User) { + await this.setRecentlyUsedTask(executeResult.task); + } if (executeResult.kind === TaskExecuteKind.Active) { let active = executeResult.active; if (active && active.same) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 906d744634..bbe073fee7 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -840,6 +840,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { setTimeout(() => this.layout(this._timeoutDimension!), 0); } } + if (!visible) { + this._widgetManager.hideHovers(); + } } public scrollDownLine(): void { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index 4e1322bd7d..bf910299ce 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -110,6 +110,8 @@ export class TerminalViewPane extends ViewPane { } else { this.layoutBody(this._bodyDimensions.height, this._bodyDimensions.width); } + } else { + this._terminalService.getActiveTab()?.setVisible(false); } })); diff --git a/src/vs/workbench/contrib/terminal/browser/widgets/widgetManager.ts b/src/vs/workbench/contrib/terminal/browser/widgets/widgetManager.ts index f8090a35cf..fd83be63aa 100644 --- a/src/vs/workbench/contrib/terminal/browser/widgets/widgetManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/widgets/widgetManager.ts @@ -5,11 +5,15 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { ITerminalWidget } from 'vs/workbench/contrib/terminal/browser/widgets/widgets'; +import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; export class TerminalWidgetManager implements IDisposable { private _container: HTMLElement | undefined; private _attached: Map = new Map(); + constructor(@IHoverService private readonly _hoverService: IHoverService) { + } + attachToElement(terminalWrapper: HTMLElement) { if (!this._container) { this._container = document.createElement('div'); @@ -25,6 +29,10 @@ export class TerminalWidgetManager implements IDisposable { } } + hideHovers(): void { + this._hoverService.hideHover(); + } + attachWidget(widget: ITerminalWidget): IDisposable | undefined { if (!this._container) { return undefined; // {{SQL CARBON EDIT}} strict-null-check diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataManualSyncView.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts similarity index 97% rename from src/vs/workbench/contrib/userDataSync/browser/userDataManualSyncView.ts rename to src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts index e6b505b73b..6d0798acbe 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataManualSyncView.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts @@ -16,7 +16,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, dispose } from 'vs/base/common/lifecycle'; import { Codicon } from 'vs/base/common/codicons'; -import { IUserDataSyncWorkbenchService, getSyncAreaLabel, IUserDataSyncPreview, IUserDataSyncResource, MANUAL_SYNC_VIEW_ID } from 'vs/workbench/services/userDataSync/common/userDataSync'; +import { IUserDataSyncWorkbenchService, getSyncAreaLabel, IUserDataSyncPreview, IUserDataSyncResource, SYNC_MERGES_VIEW_ID } from 'vs/workbench/services/userDataSync/common/userDataSync'; import { isEqual, basename } from 'vs/base/common/resources'; import { IDecorationsProvider, IDecorationData, IDecorationsService } from 'vs/workbench/services/decorations/browser/decorations'; import { IProgressService } from 'vs/platform/progress/common/progress'; @@ -39,7 +39,7 @@ import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { Severity } from 'vs/platform/notification/common/notification'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -export class UserDataManualSyncViewPane extends TreeViewPane { +export class UserDataSyncMergesViewPane extends TreeViewPane { private userDataSyncPreview: IUserDataSyncPreview; @@ -83,7 +83,7 @@ export class UserDataManualSyncViewPane extends TreeViewPane { this.createButtons(container); const that = this; - this.treeView.message = localize('explanation', "Please go through each entry and accept the change to enable sync."); + this.treeView.message = localize('explanation', "Please go through each entry and merge to enable sync."); this.treeView.dataProvider = { getChildren() { return that.getTreeItems(); } }; } @@ -164,7 +164,7 @@ export class UserDataManualSyncViewPane extends TreeViewPane { icon: Codicon.cloudDownload, menu: { id: MenuId.ViewItemContext, - when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.equals('viewItem', 'sync-resource-preview')), + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', SYNC_MERGES_VIEW_ID), ContextKeyExpr.equals('viewItem', 'sync-resource-preview')), group: 'inline', order: 1, }, @@ -184,7 +184,7 @@ export class UserDataManualSyncViewPane extends TreeViewPane { icon: Codicon.cloudUpload, menu: { id: MenuId.ViewItemContext, - when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.equals('viewItem', 'sync-resource-preview')), + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', SYNC_MERGES_VIEW_ID), ContextKeyExpr.equals('viewItem', 'sync-resource-preview')), group: 'inline', order: 2, }, @@ -204,7 +204,7 @@ export class UserDataManualSyncViewPane extends TreeViewPane { icon: Codicon.merge, menu: { id: MenuId.ViewItemContext, - when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.equals('viewItem', 'sync-resource-preview')), + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', SYNC_MERGES_VIEW_ID), ContextKeyExpr.equals('viewItem', 'sync-resource-preview')), group: 'inline', order: 3, }, @@ -224,7 +224,7 @@ export class UserDataManualSyncViewPane extends TreeViewPane { icon: Codicon.discard, menu: { id: MenuId.ViewItemContext, - when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', MANUAL_SYNC_VIEW_ID), ContextKeyExpr.or(ContextKeyExpr.equals('viewItem', 'sync-resource-accepted'), ContextKeyExpr.equals('viewItem', 'sync-resource-conflict'))), + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', SYNC_MERGES_VIEW_ID), ContextKeyExpr.or(ContextKeyExpr.equals('viewItem', 'sync-resource-accepted'), ContextKeyExpr.equals('viewItem', 'sync-resource-conflict'))), group: 'inline', order: 3, }, @@ -270,7 +270,7 @@ export class UserDataManualSyncViewPane extends TreeViewPane { previewResource = this.userDataSyncPreview.resources.find(({ local }) => isEqual(local, previewResource.local))!; await this.reopen(previewResource); if (previewResource.mergeState === MergeState.Conflict) { - await this.dialogService.show(Severity.Warning, localize('conflicts detected', "Conflicts Detected."), [], { + await this.dialogService.show(Severity.Warning, localize('conflicts detected', "Conflicts Detected"), [], { detail: localize('resolve', "Unable to merge due to conflicts. Please resolve them to continue.") }); } @@ -373,7 +373,7 @@ export class UserDataManualSyncViewPane extends TreeViewPane { } private withProgress(task: () => Promise): Promise { - return this.progressService.withProgress({ location: MANUAL_SYNC_VIEW_ID, delay: 500 }, task); + return this.progressService.withProgress({ location: SYNC_MERGES_VIEW_ID, delay: 500 }, task); } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts index 70679fe701..8e32e07a6f 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts @@ -30,13 +30,13 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IAction, Action } from 'vs/base/common/actions'; -import { IUserDataSyncWorkbenchService, CONTEXT_SYNC_STATE, getSyncAreaLabel, CONTEXT_ACCOUNT_STATE, AccountStatus, CONTEXT_ENABLE_ACTIVITY_VIEWS, SHOW_SYNC_LOG_COMMAND_ID, CONFIGURE_SYNC_COMMAND_ID, MANUAL_SYNC_VIEW_ID, CONTEXT_ENABLE_MANUAL_SYNC_VIEW } from 'vs/workbench/services/userDataSync/common/userDataSync'; +import { IUserDataSyncWorkbenchService, CONTEXT_SYNC_STATE, getSyncAreaLabel, CONTEXT_ACCOUNT_STATE, AccountStatus, CONTEXT_ENABLE_ACTIVITY_VIEWS, SHOW_SYNC_LOG_COMMAND_ID, CONFIGURE_SYNC_COMMAND_ID, SYNC_MERGES_VIEW_ID, CONTEXT_ENABLE_SYNC_MERGES_VIEW } from 'vs/workbench/services/userDataSync/common/userDataSync'; import { IUserDataSyncMachinesService, IUserDataSyncMachine } from 'vs/platform/userDataSync/common/userDataSyncMachines'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { TreeView } from 'vs/workbench/contrib/views/browser/treeView'; import { flatten } from 'vs/base/common/arrays'; -import { UserDataManualSyncViewPane } from 'vs/workbench/contrib/userDataSync/browser/userDataManualSyncView'; +import { UserDataSyncMergesViewPane } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView'; export class UserDataSyncViewPaneContainer extends ViewPaneContainer { @@ -86,7 +86,7 @@ export class UserDataSyncDataViews extends Disposable { } private registerViews(container: ViewContainer): void { - this.registerManualSyncView(container); + this.registerMergesView(container); this.registerActivityView(container, true); this.registerMachinesView(container); @@ -94,17 +94,17 @@ export class UserDataSyncDataViews extends Disposable { this.registerActivityView(container, false); } - private registerManualSyncView(container: ViewContainer): void { + private registerMergesView(container: ViewContainer): void { const viewsRegistry = Registry.as(Extensions.ViewsRegistry); - const viewName = localize('manual sync', "Manual Sync"); + const viewName = localize('merges', "Merges"); viewsRegistry.registerViews([{ - id: MANUAL_SYNC_VIEW_ID, + id: SYNC_MERGES_VIEW_ID, name: viewName, - ctorDescriptor: new SyncDescriptor(UserDataManualSyncViewPane), - when: CONTEXT_ENABLE_MANUAL_SYNC_VIEW, + ctorDescriptor: new SyncDescriptor(UserDataSyncMergesViewPane), + when: CONTEXT_ENABLE_SYNC_MERGES_VIEW, canToggleVisibility: false, canMoveView: false, - treeView: this.instantiationService.createInstance(TreeView, MANUAL_SYNC_VIEW_ID, viewName), + treeView: this.instantiationService.createInstance(TreeView, SYNC_MERGES_VIEW_ID, viewName), collapsed: false, order: 100, }], container); diff --git a/src/vs/workbench/services/editor/common/editorOpenWith.ts b/src/vs/workbench/services/editor/common/editorOpenWith.ts index d19e16ed7a..7282621db2 100644 --- a/src/vs/workbench/services/editor/common/editorOpenWith.ts +++ b/src/vs/workbench/services/editor/common/editorOpenWith.ts @@ -49,7 +49,12 @@ export async function openEditorWith( return undefined; // {{SQL CARBON EDIT}} strict-null-checks } - const overrideToUse = typeof id === 'string' && allEditorOverrides.find(([_, entry]) => entry.id === id); + let overrideToUse; + if (typeof id === 'string') { + overrideToUse = allEditorOverrides.find(([_, entry]) => entry.id === id); + } else if (allEditorOverrides.length === 1) { + overrideToUse = allEditorOverrides[0]; + } if (overrideToUse) { return overrideToUse[0].open(input, overrideOptions, group, OpenEditorContext.NEW_EDITOR)?.override; } diff --git a/src/vs/workbench/services/extensions/common/extensionHostManager.ts b/src/vs/workbench/services/extensions/common/extensionHostManager.ts index adbe01518e..2e75f1d28c 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostManager.ts @@ -184,6 +184,7 @@ export class ExtensionHostManager extends Disposable { getProxy: (identifier: ProxyIdentifier): T => this._rpcProtocol!.getProxy(identifier), set: (identifier: ProxyIdentifier, instance: R): R => this._rpcProtocol!.set(identifier, instance), assertRegistered: (identifiers: ProxyIdentifier[]): void => this._rpcProtocol!.assertRegistered(identifiers), + drain: (): Promise => this._rpcProtocol!.drain(), }; // Named customers diff --git a/src/vs/workbench/services/extensions/common/proxyIdentifier.ts b/src/vs/workbench/services/extensions/common/proxyIdentifier.ts index b8de7fdfdc..04378c9a84 100644 --- a/src/vs/workbench/services/extensions/common/proxyIdentifier.ts +++ b/src/vs/workbench/services/extensions/common/proxyIdentifier.ts @@ -18,6 +18,11 @@ export interface IRPCProtocol { * Assert these identifiers are already registered via `.set`. */ assertRegistered(identifiers: ProxyIdentifier[]): void; + + /** + * Wait for the write buffer (if applicable) to become empty. + */ + drain(): Promise; } export class ProxyIdentifier { diff --git a/src/vs/workbench/services/extensions/common/rpcProtocol.ts b/src/vs/workbench/services/extensions/common/rpcProtocol.ts index 5631263fed..be01321cdd 100644 --- a/src/vs/workbench/services/extensions/common/rpcProtocol.ts +++ b/src/vs/workbench/services/extensions/common/rpcProtocol.ts @@ -115,6 +115,13 @@ export class RPCProtocol extends Disposable implements IRPCProtocol { }); } + public drain(): Promise { + if (typeof this._protocol.drain === 'function') { + return this._protocol.drain(); + } + return Promise.resolve(); + } + private _onWillSendRequest(req: number): void { if (this._unacknowledgedCount === 0) { // Since this is the first request we are sending in a while, diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index 8bbc69a707..4aba981575 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -96,10 +96,10 @@ let onTerminate = function () { nativeExit(); }; -function _createExtHostProtocol(): Promise { +function _createExtHostProtocol(): Promise { if (process.env.VSCODE_EXTHOST_WILL_SEND_SOCKET) { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { let protocol: PersistentProtocol | null = null; @@ -163,7 +163,7 @@ function _createExtHostProtocol(): Promise { const pipeName = process.env.VSCODE_IPC_HOOK_EXTHOST!; - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const socket = net.createConnection(pipeName, () => { socket.removeListener('error', reject); @@ -203,6 +203,10 @@ async function createExtHostProtocol(): Promise { protocol.send(msg); } } + + drain(): Promise { + return protocol.drain(); + } }; } diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index ca8871e31d..e75b8365d7 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -6,7 +6,7 @@ import { IUserDataSyncService, IAuthenticationProvider, getUserDataSyncStore, isAuthenticationProvider, IUserDataAutoSyncService, SyncResource, IResourcePreview, ISyncResourcePreview, Change, IManualSyncTask } from 'vs/platform/userDataSync/common/userDataSync'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IUserDataSyncWorkbenchService, IUserDataSyncAccount, AccountStatus, CONTEXT_SYNC_ENABLEMENT, CONTEXT_SYNC_STATE, CONTEXT_ACCOUNT_STATE, SHOW_SYNC_LOG_COMMAND_ID, getSyncAreaLabel, IUserDataSyncPreview, IUserDataSyncResource, CONTEXT_ENABLE_MANUAL_SYNC_VIEW, MANUAL_SYNC_VIEW_ID, CONTEXT_ENABLE_ACTIVITY_VIEWS, SYNC_VIEW_CONTAINER_ID } from 'vs/workbench/services/userDataSync/common/userDataSync'; +import { IUserDataSyncWorkbenchService, IUserDataSyncAccount, AccountStatus, CONTEXT_SYNC_ENABLEMENT, CONTEXT_SYNC_STATE, CONTEXT_ACCOUNT_STATE, SHOW_SYNC_LOG_COMMAND_ID, getSyncAreaLabel, IUserDataSyncPreview, IUserDataSyncResource, CONTEXT_ENABLE_SYNC_MERGES_VIEW, SYNC_MERGES_VIEW_ID, CONTEXT_ENABLE_ACTIVITY_VIEWS, SYNC_VIEW_CONTAINER_ID } from 'vs/workbench/services/userDataSync/common/userDataSync'; import { AuthenticationSession, AuthenticationSessionsChangeEvent } from 'vs/editor/common/modes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; @@ -79,7 +79,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat private readonly syncEnablementContext: IContextKey; private readonly syncStatusContext: IContextKey; private readonly accountStatusContext: IContextKey; - private readonly manualSyncViewEnablementContext: IContextKey; + private readonly mergesViewEnablementContext: IContextKey; private readonly activityViewsEnablementContext: IContextKey; readonly userDataSyncPreview: UserDataSyncPreview = this._register(new UserDataSyncPreview(this.userDataSyncService)); @@ -110,7 +110,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat this.syncStatusContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService); this.accountStatusContext = CONTEXT_ACCOUNT_STATE.bindTo(contextKeyService); this.activityViewsEnablementContext = CONTEXT_ENABLE_ACTIVITY_VIEWS.bindTo(contextKeyService); - this.manualSyncViewEnablementContext = CONTEXT_ENABLE_MANUAL_SYNC_VIEW.bindTo(contextKeyService); + this.mergesViewEnablementContext = CONTEXT_ENABLE_SYNC_MERGES_VIEW.bindTo(contextKeyService); if (this.authenticationProviders.length) { @@ -302,16 +302,16 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat const result = await this.dialogService.show( Severity.Info, - localize('preferences sync', "Preferences Sync"), + localize('merge or replace', "Merge or Replace"), [ localize('merge', "Merge"), localize('replace local', "Replace Local"), - localize('sync manually', "Sync Manually..."), + localize('merge Manually', "Merge Manually..."), localize('cancel', "Cancel"), ], { cancelId: 3, - detail: localize('first time sync detail', "It looks like you last synced from another machine.\nWould you like to replace or merge with your data in the cloud or sync manually?"), + detail: localize('first time sync detail', "It looks like you last synced from another machine.\nWould you like to merge or replace with your data in the cloud?"), } ); switch (result.choice) { @@ -333,18 +333,18 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat const visibleViewContainer = this.viewsService.getVisibleViewContainer(ViewContainerLocation.Sidebar); this.userDataSyncPreview.setManualSyncPreview(task, preview); - this.manualSyncViewEnablementContext.set(true); + this.mergesViewEnablementContext.set(true); await this.waitForActiveSyncViews(); - await this.viewsService.openView(MANUAL_SYNC_VIEW_ID); + await this.viewsService.openView(SYNC_MERGES_VIEW_ID); const error = await Event.toPromise(this.userDataSyncPreview.onDidCompleteManualSync); this.userDataSyncPreview.unsetManualSyncPreview(); - this.manualSyncViewEnablementContext.set(false); + this.mergesViewEnablementContext.set(false); if (visibleViewContainer) { this.viewsService.openViewContainer(visibleViewContainer.id); } else { - const viewContainer = this.viewDescriptorService.getViewContainerByViewId(MANUAL_SYNC_VIEW_ID); + const viewContainer = this.viewDescriptorService.getViewContainerByViewId(SYNC_MERGES_VIEW_ID); this.viewsService.closeViewContainer(viewContainer!.id); } diff --git a/src/vs/workbench/services/userDataSync/common/userDataSync.ts b/src/vs/workbench/services/userDataSync/common/userDataSync.ts index 811314b4c8..5e9cb42b73 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSync.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSync.ts @@ -82,7 +82,7 @@ export const CONTEXT_SYNC_STATE = new RawContextKey('syncStatus', SyncSt export const CONTEXT_SYNC_ENABLEMENT = new RawContextKey('syncEnabled', false); export const CONTEXT_ACCOUNT_STATE = new RawContextKey('userDataSyncAccountStatus', AccountStatus.Uninitialized); export const CONTEXT_ENABLE_ACTIVITY_VIEWS = new RawContextKey(`enableSyncActivityViews`, false); -export const CONTEXT_ENABLE_MANUAL_SYNC_VIEW = new RawContextKey(`enableManualSyncView`, false); +export const CONTEXT_ENABLE_SYNC_MERGES_VIEW = new RawContextKey(`enableSyncMergesView`, false); // Commands export const CONFIGURE_SYNC_COMMAND_ID = 'workbench.userDataSync.actions.configure'; @@ -90,4 +90,4 @@ export const SHOW_SYNC_LOG_COMMAND_ID = 'workbench.userDataSync.actions.showLog' // VIEWS export const SYNC_VIEW_CONTAINER_ID = 'workbench.view.sync'; -export const MANUAL_SYNC_VIEW_ID = 'workbench.views.manualSyncView'; +export const SYNC_MERGES_VIEW_ID = 'workbench.views.sync.merges'; diff --git a/src/vs/workbench/test/browser/api/extHostDiagnostics.test.ts b/src/vs/workbench/test/browser/api/extHostDiagnostics.test.ts index 5a0b4445ce..3c8d905d59 100644 --- a/src/vs/workbench/test/browser/api/extHostDiagnostics.test.ts +++ b/src/vs/workbench/test/browser/api/extHostDiagnostics.test.ts @@ -389,6 +389,9 @@ suite('ExtHostDiagnostics', () => { assertRegistered(): void { } + drain() { + return undefined!; + } }, new NullLogService()); let collection1 = diags.createDiagnosticCollection(nullExtensionDescription.identifier, 'foo'); @@ -438,6 +441,9 @@ suite('ExtHostDiagnostics', () => { assertRegistered(): void { } + drain() { + return undefined!; + } }, new NullLogService()); diff --git a/src/vs/workbench/test/browser/api/extHostFileSystemEventService.test.ts b/src/vs/workbench/test/browser/api/extHostFileSystemEventService.test.ts index 094657fa5a..70adb5a277 100644 --- a/src/vs/workbench/test/browser/api/extHostFileSystemEventService.test.ts +++ b/src/vs/workbench/test/browser/api/extHostFileSystemEventService.test.ts @@ -15,7 +15,8 @@ suite('ExtHostFileSystemEventService', () => { const protocol: IMainContext = { getProxy: () => { return undefined!; }, set: undefined!, - assertRegistered: undefined! + assertRegistered: undefined!, + drain: undefined! }; const watcher1 = new ExtHostFileSystemEventService(protocol, new NullLogService(), undefined!).createFileSystemWatcher('**/somethingInteresting', false, false, false); diff --git a/src/vs/workbench/test/browser/api/extHostWorkspace.test.ts b/src/vs/workbench/test/browser/api/extHostWorkspace.test.ts index c6481233f8..ea4606745f 100644 --- a/src/vs/workbench/test/browser/api/extHostWorkspace.test.ts +++ b/src/vs/workbench/test/browser/api/extHostWorkspace.test.ts @@ -298,7 +298,8 @@ suite('ExtHostWorkspace', function () { const protocol: IMainContext = { getProxy: () => { return undefined!; }, set: () => { return undefined!; }, - assertRegistered: () => { } + assertRegistered: () => { }, + drain: () => { return undefined!; }, }; const ws = createExtHostWorkspace(protocol, { id: 'foo', name: 'Test', folders: [] }, new NullLogService()); diff --git a/src/vs/workbench/test/browser/api/mainThreadDiagnostics.test.ts b/src/vs/workbench/test/browser/api/mainThreadDiagnostics.test.ts index 9e934f033f..990c98bdc7 100644 --- a/src/vs/workbench/test/browser/api/mainThreadDiagnostics.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadDiagnostics.test.ts @@ -32,6 +32,7 @@ suite('MainThreadDiagnostics', function () { $acceptMarkersChange() { } }; } + drain(): any { return null; } }, markerService, new class extends mock() { diff --git a/src/vs/workbench/test/browser/api/mainThreadTreeViews.test.ts b/src/vs/workbench/test/browser/api/mainThreadTreeViews.test.ts index 2ac1c9bbe0..db70d8a2a0 100644 --- a/src/vs/workbench/test/browser/api/mainThreadTreeViews.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadTreeViews.test.ts @@ -66,6 +66,7 @@ suite('MainThreadHostTreeView', function () { getProxy(): any { return extHostTreeViewsShape; } + drain(): any { return null; } }, new TestViewsService(), new TestNotificationService(), testExtensionService, new NullLogService()); mainThreadTreeViews.$registerTreeViewDataProvider(testTreeViewId, { showCollapseAll: false, canSelectMany: false }); await testExtensionService.whenInstalledExtensionsRegistered(); diff --git a/src/vs/workbench/test/browser/api/testRPCProtocol.ts b/src/vs/workbench/test/browser/api/testRPCProtocol.ts index 4a5829d377..b41002a4bb 100644 --- a/src/vs/workbench/test/browser/api/testRPCProtocol.ts +++ b/src/vs/workbench/test/browser/api/testRPCProtocol.ts @@ -19,7 +19,8 @@ export function SingleProxyRPCProtocol(thing: any): IExtHostContext & IExtHostRp set(identifier: ProxyIdentifier, value: R): R { return value; }, - assertRegistered: undefined! + assertRegistered: undefined!, + drain: undefined! }; } @@ -40,6 +41,10 @@ export class TestRPCProtocol implements IExtHostContext, IExtHostRpcService { this._proxies = Object.create(null); } + drain(): Promise { + return Promise.resolve(); + } + private get _callCount(): number { return this._callCountValue; }