From 9824af2c7b3833689965064039871c74224d6500 Mon Sep 17 00:00:00 2001 From: gjsjohnmurray Date: Mon, 11 Dec 2023 17:52:06 +0000 Subject: [PATCH 01/26] Fix `Customize Layout` bugs related to Activity Bar (fix #200571) --- src/vs/workbench/browser/actions/layoutActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 4a2a2ad5dc6..1d54794a87b 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -1360,7 +1360,7 @@ if (!isMacintosh || !isNative) { } ToggleVisibilityActions.push(...[ - CreateToggleLayoutItem(ToggleActivityBarVisibilityActionId, ContextKeyExpr.equals('config.workbench.activityBar.visible', true), localize('activityBar', "Activity Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: activityBarLeftIcon, iconB: activityBarRightIcon }), + CreateToggleLayoutItem(ToggleActivityBarVisibilityActionId, ContextKeyExpr.notEquals('config.workbench.activityBar.location', 'hidden'), localize('activityBar', "Activity Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: activityBarLeftIcon, iconB: activityBarRightIcon }), CreateToggleLayoutItem(ToggleSidebarVisibilityAction.ID, SideBarVisibleContext, localize('sideBar', "Primary Side Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: panelLeftIcon, iconB: panelRightIcon }), CreateToggleLayoutItem(ToggleAuxiliaryBarAction.ID, AuxiliaryBarVisibleContext, localize('secondarySideBar', "Secondary Side Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: panelRightIcon, iconB: panelLeftIcon }), CreateToggleLayoutItem(TogglePanelAction.ID, PanelVisibleContext, localize('panel', "Panel"), panelIcon), @@ -1548,7 +1548,7 @@ registerAction2(class CustomizeLayoutAction extends Action2 { }; // Reset all layout options - resetSetting('workbench.activityBar.visible'); + resetSetting('workbench.activityBar.location'); resetSetting('workbench.sideBar.location'); resetSetting('workbench.statusBar.visible'); resetSetting('workbench.panel.defaultLocation'); From d4e9b630736e548155c3e4222ba013954e273868 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 20 Dec 2023 10:09:50 -0800 Subject: [PATCH 02/26] Update xterm Fixes #199848 --- package.json | 16 +++++------ remote/package.json | 16 +++++------ remote/web/package.json | 14 ++++----- remote/web/yarn.lock | 56 ++++++++++++++++++------------------ remote/yarn.lock | 64 ++++++++++++++++++++--------------------- yarn.lock | 64 ++++++++++++++++++++--------------------- 6 files changed, 115 insertions(+), 115 deletions(-) diff --git a/package.json b/package.json index 7281d691d16..e1d479a26eb 100644 --- a/package.json +++ b/package.json @@ -80,14 +80,14 @@ "@vscode/windows-mutex": "^0.4.4", "@vscode/windows-process-tree": "^0.5.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-canvas": "0.6.0-beta.19", - "@xterm/addon-image": "0.7.0-beta.17", - "@xterm/addon-search": "0.14.0-beta.19", - "@xterm/addon-serialize": "0.12.0-beta.19", - "@xterm/addon-unicode11": "0.7.0-beta.19", - "@xterm/addon-webgl": "0.17.0-beta.19", - "@xterm/headless": "5.4.0-beta.19", - "@xterm/xterm": "5.4.0-beta.19", + "@xterm/addon-canvas": "0.6.0-beta.20", + "@xterm/addon-image": "0.7.0-beta.18", + "@xterm/addon-search": "0.14.0-beta.20", + "@xterm/addon-serialize": "0.12.0-beta.20", + "@xterm/addon-unicode11": "0.7.0-beta.20", + "@xterm/addon-webgl": "0.17.0-beta.20", + "@xterm/headless": "5.4.0-beta.20", + "@xterm/xterm": "5.4.0-beta.20", "graceful-fs": "4.2.11", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", diff --git a/remote/package.json b/remote/package.json index 47c6565b955..07d070cc8f2 100644 --- a/remote/package.json +++ b/remote/package.json @@ -13,14 +13,14 @@ "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.5.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-canvas": "0.6.0-beta.19", - "@xterm/addon-image": "0.7.0-beta.17", - "@xterm/addon-search": "0.14.0-beta.19", - "@xterm/addon-serialize": "0.12.0-beta.19", - "@xterm/addon-unicode11": "0.7.0-beta.19", - "@xterm/addon-webgl": "0.17.0-beta.19", - "@xterm/headless": "5.4.0-beta.19", - "@xterm/xterm": "5.4.0-beta.19", + "@xterm/addon-canvas": "0.6.0-beta.20", + "@xterm/addon-image": "0.7.0-beta.18", + "@xterm/addon-search": "0.14.0-beta.20", + "@xterm/addon-serialize": "0.12.0-beta.20", + "@xterm/addon-unicode11": "0.7.0-beta.20", + "@xterm/addon-webgl": "0.17.0-beta.20", + "@xterm/headless": "5.4.0-beta.20", + "@xterm/xterm": "5.4.0-beta.20", "cookie": "^0.4.0", "graceful-fs": "4.2.11", "http-proxy-agent": "^7.0.0", diff --git a/remote/web/package.json b/remote/web/package.json index d441046c579..8bedb5c4f10 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -7,13 +7,13 @@ "@microsoft/1ds-post-js": "^3.2.13", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-canvas": "0.6.0-beta.19", - "@xterm/addon-image": "0.7.0-beta.17", - "@xterm/addon-search": "0.14.0-beta.19", - "@xterm/addon-serialize": "0.12.0-beta.19", - "@xterm/addon-unicode11": "0.7.0-beta.19", - "@xterm/addon-webgl": "0.17.0-beta.19", - "@xterm/xterm": "5.4.0-beta.19", + "@xterm/addon-canvas": "0.6.0-beta.20", + "@xterm/addon-image": "0.7.0-beta.18", + "@xterm/addon-search": "0.14.0-beta.20", + "@xterm/addon-serialize": "0.12.0-beta.20", + "@xterm/addon-unicode11": "0.7.0-beta.20", + "@xterm/addon-webgl": "0.17.0-beta.20", + "@xterm/xterm": "5.4.0-beta.20", "jschardet": "3.0.0", "tas-client-umd": "0.1.8", "vscode-oniguruma": "1.7.0", diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 35e696bdf49..f540fcee68f 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -48,40 +48,40 @@ resolved "https://registry.yarnpkg.com/@vscode/vscode-languagedetection/-/vscode-languagedetection-1.0.21.tgz#89b48f293f6aa3341bb888c1118d16ff13b032d3" integrity sha512-zSUH9HYCw5qsCtd7b31yqkpaCU6jhtkKLkvOOA8yTrIRfBSOFb8PPhgmMicD7B/m+t4PwOJXzU1XDtrM9Fd3/g== -"@xterm/addon-canvas@0.6.0-beta.19": - version "0.6.0-beta.19" - resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.19.tgz#5f8896b884d2a558a28eb197f7f4766ac37d33a0" - integrity sha512-i+26DqYgI/PZViCiEK4Vu8c4Fi5J0i+TwnFfBDLcumHH07Al1uRd5BRaVg/i93vk6bRyEIOiOiIToXSf37ov/w== +"@xterm/addon-canvas@0.6.0-beta.20": + version "0.6.0-beta.20" + resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.20.tgz#078dddef70caf880b2cb121fdda37d301fc13156" + integrity sha512-tHhsuqElE7LNiDJPbZzgVpmbcG2Dk6i2vh1EI+DzSByUWScDqLoeJbVPE5Xd2UW2garo24lxErpnIAlsytcA3A== -"@xterm/addon-image@0.7.0-beta.17": - version "0.7.0-beta.17" - resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.17.tgz#bc3d6f09619ef1f3b68c7009d29ee14003a26b30" - integrity sha512-nnHVoYVoh+CpT4FQN/ALKesr96YvdVNUzQRQo4aAARUKst5DFaHQX9Yn/qLDN5s0WCqI3bgIEo8UAakfHITumA== +"@xterm/addon-image@0.7.0-beta.18": + version "0.7.0-beta.18" + resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.18.tgz#588ea2d0841cff48c63bde1bfcdf56e9494dc6af" + integrity sha512-+HQ+IBmHPelzjRJ5zO3XkjbeQNr2Zrf5wAlbPhy4EGSD0mDCqHJSfzZ8wKrhx7t8qpfiA8eTpWu/M76WsEnlnA== -"@xterm/addon-search@0.14.0-beta.19": - version "0.14.0-beta.19" - resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.19.tgz#2bc13378765f0d5e72d9bcb7887e23eee31de7eb" - integrity sha512-Y1pPdtdZj0xRQ/Is4jdO0dyZe+uM6AhWi3v2U4sdJmhz2mxVe/HAKBHkx6tyfMtX9ge/9ZYajd/Sy8rkjIFdmQ== +"@xterm/addon-search@0.14.0-beta.20": + version "0.14.0-beta.20" + resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.20.tgz#cac366b1be1eb02cf9fe9537933f26f227d030c8" + integrity sha512-1LOL/OzWSrCBpndiBeeE2S1rxtKKgU1ucYFSG3P68W0J4VQz/Ksci1BgDKsgspj9jzpsGhdql3zwa5WEM7n4Pg== -"@xterm/addon-serialize@0.12.0-beta.19": - version "0.12.0-beta.19" - resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.19.tgz#465a9525a420b8f0d12eed419d6e051f9a887814" - integrity sha512-3v6a4/4gxAwoyJsBp6vJBofymgTH8paSHl8K2uQfFuLosOavNyCtrNPiNne7tpppK6t8zCDJ/mVrXNptz4Mlsg== +"@xterm/addon-serialize@0.12.0-beta.20": + version "0.12.0-beta.20" + resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.20.tgz#5fe126194ff4dc466b92a0946e081e039a14ad21" + integrity sha512-GdRCQDjLyVNBxCFnhfCWsMmuqv2PryUkOaNl4z5MqB5lBUkiEnRNY0u/s5f34+2zrijp3h0O/f9JDLW4gSUQgw== -"@xterm/addon-unicode11@0.7.0-beta.19": - version "0.7.0-beta.19" - resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.19.tgz#0e81775f84346a97c657fb4cd7702de1cfe4b83f" - integrity sha512-U5O+JLklO4qtptWAWUw14QRWdalLl0bFAQxLKuTtDmusgfn33pNDRD6RH3R+IHhO2e6svAwrw27OcCEcdn0AJQ== +"@xterm/addon-unicode11@0.7.0-beta.20": + version "0.7.0-beta.20" + resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.20.tgz#5d3c97320898dd6766f2dc127deb4f071c8698c2" + integrity sha512-4/uwJ6lV/xJplT7hJc7sO4Im4XNvEXHnUEFIs03FFp8ZUfu3U6wcBk6/GoKMwJKJtGVNxotiD6ZzJ5v8IBH6nA== -"@xterm/addon-webgl@0.17.0-beta.19": - version "0.17.0-beta.19" - resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.19.tgz#fefe44c20b4d4d070363e03ae33087505a88807e" - integrity sha512-L59l9Cd4KTMCwnw2HPi/cUgH4iL5dK7VERK/wSTWEGYMOi3WGTmXqsg8ftfR2jFC9P33eqYNVJDmbr+pF0XsDg== +"@xterm/addon-webgl@0.17.0-beta.20": + version "0.17.0-beta.20" + resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.20.tgz#443845ac5ac755cf762b105ed237b30426b07137" + integrity sha512-iqvXNSTfKIcO9FBraNwdO/ixPrTHok8CBN/wjlnGLv0ZMc4zLAiKE8+PHyg9ZY38QJfS+4Ouo8KsuZwoOYfnNA== -"@xterm/xterm@5.4.0-beta.19": - version "5.4.0-beta.19" - resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.19.tgz#5c9c5cacd0cf2a0719086ce03ab035480b1cfd96" - integrity sha512-Rx/Y/y3YGjpiW6IUq8UlE6qrTYuUlEfpVg/BS6kIPr8/cUOchE1fsKWCMxz/u2bIyQyEovRi892iYcQJ4scstw== +"@xterm/xterm@5.4.0-beta.20": + version "5.4.0-beta.20" + resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.20.tgz#28bbbbc73eceb6ef3e1e095de195cf849d0cbfb6" + integrity sha512-nkY91qBy5pe1HlW9LOoLcyG6v4teEsliEtUVshAO42NrJDaPniSn28O5m5832UjZOdjLCY58QlcBkZUquODGrQ== jschardet@3.0.0: version "3.0.0" diff --git a/remote/yarn.lock b/remote/yarn.lock index b9825129439..81546b79451 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -114,45 +114,45 @@ resolved "https://registry.yarnpkg.com/@vscode/windows-registry/-/windows-registry-1.1.0.tgz#03dace7c29c46f658588b9885b9580e453ad21f9" integrity sha512-5AZzuWJpGscyiMOed0IuyEwt6iKmV5Us7zuwCDCFYMIq7tsvooO9BUiciywsvuthGz6UG4LSpeDeCxvgMVhnIw== -"@xterm/addon-canvas@0.6.0-beta.19": - version "0.6.0-beta.19" - resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.19.tgz#5f8896b884d2a558a28eb197f7f4766ac37d33a0" - integrity sha512-i+26DqYgI/PZViCiEK4Vu8c4Fi5J0i+TwnFfBDLcumHH07Al1uRd5BRaVg/i93vk6bRyEIOiOiIToXSf37ov/w== +"@xterm/addon-canvas@0.6.0-beta.20": + version "0.6.0-beta.20" + resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.20.tgz#078dddef70caf880b2cb121fdda37d301fc13156" + integrity sha512-tHhsuqElE7LNiDJPbZzgVpmbcG2Dk6i2vh1EI+DzSByUWScDqLoeJbVPE5Xd2UW2garo24lxErpnIAlsytcA3A== -"@xterm/addon-image@0.7.0-beta.17": - version "0.7.0-beta.17" - resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.17.tgz#bc3d6f09619ef1f3b68c7009d29ee14003a26b30" - integrity sha512-nnHVoYVoh+CpT4FQN/ALKesr96YvdVNUzQRQo4aAARUKst5DFaHQX9Yn/qLDN5s0WCqI3bgIEo8UAakfHITumA== +"@xterm/addon-image@0.7.0-beta.18": + version "0.7.0-beta.18" + resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.18.tgz#588ea2d0841cff48c63bde1bfcdf56e9494dc6af" + integrity sha512-+HQ+IBmHPelzjRJ5zO3XkjbeQNr2Zrf5wAlbPhy4EGSD0mDCqHJSfzZ8wKrhx7t8qpfiA8eTpWu/M76WsEnlnA== -"@xterm/addon-search@0.14.0-beta.19": - version "0.14.0-beta.19" - resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.19.tgz#2bc13378765f0d5e72d9bcb7887e23eee31de7eb" - integrity sha512-Y1pPdtdZj0xRQ/Is4jdO0dyZe+uM6AhWi3v2U4sdJmhz2mxVe/HAKBHkx6tyfMtX9ge/9ZYajd/Sy8rkjIFdmQ== +"@xterm/addon-search@0.14.0-beta.20": + version "0.14.0-beta.20" + resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.20.tgz#cac366b1be1eb02cf9fe9537933f26f227d030c8" + integrity sha512-1LOL/OzWSrCBpndiBeeE2S1rxtKKgU1ucYFSG3P68W0J4VQz/Ksci1BgDKsgspj9jzpsGhdql3zwa5WEM7n4Pg== -"@xterm/addon-serialize@0.12.0-beta.19": - version "0.12.0-beta.19" - resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.19.tgz#465a9525a420b8f0d12eed419d6e051f9a887814" - integrity sha512-3v6a4/4gxAwoyJsBp6vJBofymgTH8paSHl8K2uQfFuLosOavNyCtrNPiNne7tpppK6t8zCDJ/mVrXNptz4Mlsg== +"@xterm/addon-serialize@0.12.0-beta.20": + version "0.12.0-beta.20" + resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.20.tgz#5fe126194ff4dc466b92a0946e081e039a14ad21" + integrity sha512-GdRCQDjLyVNBxCFnhfCWsMmuqv2PryUkOaNl4z5MqB5lBUkiEnRNY0u/s5f34+2zrijp3h0O/f9JDLW4gSUQgw== -"@xterm/addon-unicode11@0.7.0-beta.19": - version "0.7.0-beta.19" - resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.19.tgz#0e81775f84346a97c657fb4cd7702de1cfe4b83f" - integrity sha512-U5O+JLklO4qtptWAWUw14QRWdalLl0bFAQxLKuTtDmusgfn33pNDRD6RH3R+IHhO2e6svAwrw27OcCEcdn0AJQ== +"@xterm/addon-unicode11@0.7.0-beta.20": + version "0.7.0-beta.20" + resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.20.tgz#5d3c97320898dd6766f2dc127deb4f071c8698c2" + integrity sha512-4/uwJ6lV/xJplT7hJc7sO4Im4XNvEXHnUEFIs03FFp8ZUfu3U6wcBk6/GoKMwJKJtGVNxotiD6ZzJ5v8IBH6nA== -"@xterm/addon-webgl@0.17.0-beta.19": - version "0.17.0-beta.19" - resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.19.tgz#fefe44c20b4d4d070363e03ae33087505a88807e" - integrity sha512-L59l9Cd4KTMCwnw2HPi/cUgH4iL5dK7VERK/wSTWEGYMOi3WGTmXqsg8ftfR2jFC9P33eqYNVJDmbr+pF0XsDg== +"@xterm/addon-webgl@0.17.0-beta.20": + version "0.17.0-beta.20" + resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.20.tgz#443845ac5ac755cf762b105ed237b30426b07137" + integrity sha512-iqvXNSTfKIcO9FBraNwdO/ixPrTHok8CBN/wjlnGLv0ZMc4zLAiKE8+PHyg9ZY38QJfS+4Ouo8KsuZwoOYfnNA== -"@xterm/headless@5.4.0-beta.19": - version "5.4.0-beta.19" - resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.4.0-beta.19.tgz#e84a7cebda581273032f82fc8c53941204bc7f93" - integrity sha512-y7Ne2G/Tgn6bHr14eBHqcq5gGFFCHKGBzmXTExT1Z4Fb6ofPACPWAo60S5B6uh49W4Ts13gQYZ5C0XEvHud0Eg== +"@xterm/headless@5.4.0-beta.20": + version "5.4.0-beta.20" + resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.4.0-beta.20.tgz#af26d3d0e2cdd615ccfac4a8660181fee19898fd" + integrity sha512-H/as1d67J43/CB8xt1Yg/eJMbq1yopwG1bDBKdsf2ro8A1PmJFXNzaDB+wSgoH42fCusSpLJvXtUvDLtqfvBTg== -"@xterm/xterm@5.4.0-beta.19": - version "5.4.0-beta.19" - resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.19.tgz#5c9c5cacd0cf2a0719086ce03ab035480b1cfd96" - integrity sha512-Rx/Y/y3YGjpiW6IUq8UlE6qrTYuUlEfpVg/BS6kIPr8/cUOchE1fsKWCMxz/u2bIyQyEovRi892iYcQJ4scstw== +"@xterm/xterm@5.4.0-beta.20": + version "5.4.0-beta.20" + resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.20.tgz#28bbbbc73eceb6ef3e1e095de195cf849d0cbfb6" + integrity sha512-nkY91qBy5pe1HlW9LOoLcyG6v4teEsliEtUVshAO42NrJDaPniSn28O5m5832UjZOdjLCY58QlcBkZUquODGrQ== agent-base@^7.0.1, agent-base@^7.0.2, agent-base@^7.1.0: version "7.1.0" diff --git a/yarn.lock b/yarn.lock index 92b0addf5da..060fe354a56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1675,45 +1675,45 @@ resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.1.tgz#34bdc31727a1889198855913db2f270ace6d7bf8" integrity sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw== -"@xterm/addon-canvas@0.6.0-beta.19": - version "0.6.0-beta.19" - resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.19.tgz#5f8896b884d2a558a28eb197f7f4766ac37d33a0" - integrity sha512-i+26DqYgI/PZViCiEK4Vu8c4Fi5J0i+TwnFfBDLcumHH07Al1uRd5BRaVg/i93vk6bRyEIOiOiIToXSf37ov/w== +"@xterm/addon-canvas@0.6.0-beta.20": + version "0.6.0-beta.20" + resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.20.tgz#078dddef70caf880b2cb121fdda37d301fc13156" + integrity sha512-tHhsuqElE7LNiDJPbZzgVpmbcG2Dk6i2vh1EI+DzSByUWScDqLoeJbVPE5Xd2UW2garo24lxErpnIAlsytcA3A== -"@xterm/addon-image@0.7.0-beta.17": - version "0.7.0-beta.17" - resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.17.tgz#bc3d6f09619ef1f3b68c7009d29ee14003a26b30" - integrity sha512-nnHVoYVoh+CpT4FQN/ALKesr96YvdVNUzQRQo4aAARUKst5DFaHQX9Yn/qLDN5s0WCqI3bgIEo8UAakfHITumA== +"@xterm/addon-image@0.7.0-beta.18": + version "0.7.0-beta.18" + resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.18.tgz#588ea2d0841cff48c63bde1bfcdf56e9494dc6af" + integrity sha512-+HQ+IBmHPelzjRJ5zO3XkjbeQNr2Zrf5wAlbPhy4EGSD0mDCqHJSfzZ8wKrhx7t8qpfiA8eTpWu/M76WsEnlnA== -"@xterm/addon-search@0.14.0-beta.19": - version "0.14.0-beta.19" - resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.19.tgz#2bc13378765f0d5e72d9bcb7887e23eee31de7eb" - integrity sha512-Y1pPdtdZj0xRQ/Is4jdO0dyZe+uM6AhWi3v2U4sdJmhz2mxVe/HAKBHkx6tyfMtX9ge/9ZYajd/Sy8rkjIFdmQ== +"@xterm/addon-search@0.14.0-beta.20": + version "0.14.0-beta.20" + resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.20.tgz#cac366b1be1eb02cf9fe9537933f26f227d030c8" + integrity sha512-1LOL/OzWSrCBpndiBeeE2S1rxtKKgU1ucYFSG3P68W0J4VQz/Ksci1BgDKsgspj9jzpsGhdql3zwa5WEM7n4Pg== -"@xterm/addon-serialize@0.12.0-beta.19": - version "0.12.0-beta.19" - resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.19.tgz#465a9525a420b8f0d12eed419d6e051f9a887814" - integrity sha512-3v6a4/4gxAwoyJsBp6vJBofymgTH8paSHl8K2uQfFuLosOavNyCtrNPiNne7tpppK6t8zCDJ/mVrXNptz4Mlsg== +"@xterm/addon-serialize@0.12.0-beta.20": + version "0.12.0-beta.20" + resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.20.tgz#5fe126194ff4dc466b92a0946e081e039a14ad21" + integrity sha512-GdRCQDjLyVNBxCFnhfCWsMmuqv2PryUkOaNl4z5MqB5lBUkiEnRNY0u/s5f34+2zrijp3h0O/f9JDLW4gSUQgw== -"@xterm/addon-unicode11@0.7.0-beta.19": - version "0.7.0-beta.19" - resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.19.tgz#0e81775f84346a97c657fb4cd7702de1cfe4b83f" - integrity sha512-U5O+JLklO4qtptWAWUw14QRWdalLl0bFAQxLKuTtDmusgfn33pNDRD6RH3R+IHhO2e6svAwrw27OcCEcdn0AJQ== +"@xterm/addon-unicode11@0.7.0-beta.20": + version "0.7.0-beta.20" + resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.20.tgz#5d3c97320898dd6766f2dc127deb4f071c8698c2" + integrity sha512-4/uwJ6lV/xJplT7hJc7sO4Im4XNvEXHnUEFIs03FFp8ZUfu3U6wcBk6/GoKMwJKJtGVNxotiD6ZzJ5v8IBH6nA== -"@xterm/addon-webgl@0.17.0-beta.19": - version "0.17.0-beta.19" - resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.19.tgz#fefe44c20b4d4d070363e03ae33087505a88807e" - integrity sha512-L59l9Cd4KTMCwnw2HPi/cUgH4iL5dK7VERK/wSTWEGYMOi3WGTmXqsg8ftfR2jFC9P33eqYNVJDmbr+pF0XsDg== +"@xterm/addon-webgl@0.17.0-beta.20": + version "0.17.0-beta.20" + resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.20.tgz#443845ac5ac755cf762b105ed237b30426b07137" + integrity sha512-iqvXNSTfKIcO9FBraNwdO/ixPrTHok8CBN/wjlnGLv0ZMc4zLAiKE8+PHyg9ZY38QJfS+4Ouo8KsuZwoOYfnNA== -"@xterm/headless@5.4.0-beta.19": - version "5.4.0-beta.19" - resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.4.0-beta.19.tgz#e84a7cebda581273032f82fc8c53941204bc7f93" - integrity sha512-y7Ne2G/Tgn6bHr14eBHqcq5gGFFCHKGBzmXTExT1Z4Fb6ofPACPWAo60S5B6uh49W4Ts13gQYZ5C0XEvHud0Eg== +"@xterm/headless@5.4.0-beta.20": + version "5.4.0-beta.20" + resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.4.0-beta.20.tgz#af26d3d0e2cdd615ccfac4a8660181fee19898fd" + integrity sha512-H/as1d67J43/CB8xt1Yg/eJMbq1yopwG1bDBKdsf2ro8A1PmJFXNzaDB+wSgoH42fCusSpLJvXtUvDLtqfvBTg== -"@xterm/xterm@5.4.0-beta.19": - version "5.4.0-beta.19" - resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.19.tgz#5c9c5cacd0cf2a0719086ce03ab035480b1cfd96" - integrity sha512-Rx/Y/y3YGjpiW6IUq8UlE6qrTYuUlEfpVg/BS6kIPr8/cUOchE1fsKWCMxz/u2bIyQyEovRi892iYcQJ4scstw== +"@xterm/xterm@5.4.0-beta.20": + version "5.4.0-beta.20" + resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.20.tgz#28bbbbc73eceb6ef3e1e095de195cf849d0cbfb6" + integrity sha512-nkY91qBy5pe1HlW9LOoLcyG6v4teEsliEtUVshAO42NrJDaPniSn28O5m5832UjZOdjLCY58QlcBkZUquODGrQ== "@xtuc/ieee754@^1.2.0": version "1.2.0" From 5764957b73f700f2dd9077f052af0592b3e1fe51 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 20 Dec 2023 10:55:36 -0800 Subject: [PATCH 03/26] Add move terminal into new window action/menu item Fixes #199845 --- .../workbench/contrib/terminal/browser/terminal.ts | 8 +++++--- .../contrib/terminal/browser/terminalActions.ts | 8 ++++++++ .../contrib/terminal/browser/terminalMenus.ts | 11 +++++++++++ .../contrib/terminal/browser/terminalService.ts | 12 +++++++++--- src/vs/workbench/contrib/terminal/common/terminal.ts | 1 + .../contrib/terminal/common/terminalStrings.ts | 4 ++++ 6 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index dcfc52dfb3e..f5dfc7d63a0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -22,11 +22,12 @@ import { IEditableData } from 'vs/workbench/common/views'; import { ITerminalStatusList } from 'vs/workbench/contrib/terminal/browser/terminalStatusList'; import { XtermTerminal } from 'vs/workbench/contrib/terminal/browser/xterm/xtermTerminal'; import { IRegisterContributedProfileArgs, IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalConfiguration, ITerminalFont, ITerminalProcessExtHostProxy, ITerminalProcessInfo } from 'vs/workbench/contrib/terminal/common/terminal'; -import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { ISimpleSelectedSuggestion } from 'vs/workbench/services/suggest/browser/simpleSuggestWidget'; import type { IMarker, ITheme, Terminal as RawXtermTerminal } from '@xterm/xterm'; import { ScrollPosition } from 'vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { GroupIdentifier } from 'vs/workbench/common/editor'; +import { ACTIVE_GROUP_TYPE, AUX_WINDOW_GROUP_TYPE, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; export const ITerminalService = createDecorator('terminalService'); export const ITerminalEditorService = createDecorator('terminalEditorService'); @@ -297,7 +298,8 @@ export interface ITerminalService extends ITerminalInstanceHost { getActiveOrCreateInstance(options?: { acceptsInput?: boolean }): Promise; revealActiveTerminal(preserveFocus?: boolean): Promise; - moveToEditor(source: ITerminalInstance): void; + moveToEditor(source: ITerminalInstance, group?: GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | AUX_WINDOW_GROUP_TYPE): void; + moveIntoNewEditor(source: ITerminalInstance): void; moveToTerminalView(source: ITerminalInstance | URI): Promise; getPrimaryBackend(): ITerminalBackend | undefined; @@ -419,7 +421,7 @@ export interface ICreateTerminalOptions { } export interface TerminalEditorLocation { - viewColumn: EditorGroupColumn; + viewColumn: GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | AUX_WINDOW_GROUP_TYPE; preserveFocus?: boolean; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 5112c6fcdad..a169a4b3154 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -366,6 +366,14 @@ export function registerTerminalActions() { runAfter: (instances) => instances.at(-1)?.focus() }); + registerContextualInstanceAction({ + id: TerminalCommandId.MoveIntoNewWindow, + title: terminalStrings.moveIntoNewWindow, + precondition: sharedWhenClause.terminalAvailable_and_opened, + run: (instance, c) => c.service.moveIntoNewEditor(instance), + runAfter: (instances) => instances.at(-1)?.focus() + }); + registerTerminalAction({ id: TerminalCommandId.MoveToTerminalPanel, title: terminalStrings.moveToTerminalPanel, diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts index 4ec76c893c1..c9097e71561 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts @@ -576,6 +576,17 @@ export function setupTerminalMenus(): void { order: 2 } }, + { + id: MenuId.TerminalTabContext, + item: { + command: { + id: TerminalCommandId.MoveIntoNewWindow, + title: terminalStrings.moveIntoNewWindow.value + }, + group: ContextMenuGroup.Create, + order: 2 + } + }, { id: MenuId.TerminalTabContext, item: { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index fdebc10602b..bff4a07ecab 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -41,7 +41,7 @@ import { IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalP import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { columnToEditorGroup } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { ACTIVE_GROUP_TYPE, AUX_WINDOW_GROUP, AUX_WINDOW_GROUP_TYPE, IEditorService, SIDE_GROUP, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ILifecycleService, ShutdownReason, StartupKind, WillShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -56,6 +56,7 @@ import { DetachedTerminal } from 'vs/workbench/contrib/terminal/browser/detached import { ITerminalCapabilityImplMap, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { createInstanceCapabilityEventMultiplexer } from 'vs/workbench/contrib/terminal/browser/terminalEvents'; import { mainWindow } from 'vs/base/browser/window'; +import { GroupIdentifier } from 'vs/workbench/common/editor'; export class TerminalService extends Disposable implements ITerminalService { declare _serviceBrand: undefined; @@ -762,7 +763,7 @@ export class TerminalService extends Disposable implements ITerminalService { return this.instances.some(term => term.processId === remoteTerm.pid); } - moveToEditor(source: ITerminalInstance): void { + moveToEditor(source: ITerminalInstance, group?: GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | AUX_WINDOW_GROUP_TYPE): void { if (source.target === TerminalLocation.Editor) { return; } @@ -771,7 +772,12 @@ export class TerminalService extends Disposable implements ITerminalService { return; } sourceGroup.removeInstance(source); - this._terminalEditorService.openEditor(source); + this._terminalEditorService.openEditor(source, group ? { viewColumn: group } : undefined); + + } + + moveIntoNewEditor(source: ITerminalInstance): void { + this.moveToEditor(source, AUX_WINDOW_GROUP); } async moveToTerminalView(source?: ITerminalInstance | URI, target?: ITerminalInstance, side?: 'before' | 'after'): Promise { diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index eb876639bf5..51525b5ca39 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -481,6 +481,7 @@ export const enum TerminalCommandId { DetachSession = 'workbench.action.terminal.detachSession', MoveToEditor = 'workbench.action.terminal.moveToEditor', MoveToTerminalPanel = 'workbench.action.terminal.moveToTerminalPanel', + MoveIntoNewWindow = 'workbench.action.terminal.moveIntoNewWindow', SetDimensions = 'workbench.action.terminal.setDimensions', ClearPreviousSessionHistory = 'workbench.action.terminal.clearPreviousSessionHistory', SelectPrevSuggestion = 'workbench.action.terminal.selectPrevSuggestion', diff --git a/src/vs/workbench/contrib/terminal/common/terminalStrings.ts b/src/vs/workbench/contrib/terminal/common/terminalStrings.ts index ebe0795a239..a1003b54822 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalStrings.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalStrings.ts @@ -37,6 +37,10 @@ export const terminalStrings = { value: localize('moveToEditor', "Move Terminal into Editor Area"), original: 'Move Terminal into Editor Area', }, + moveIntoNewWindow: { + value: localize('moveIntoNewWindow', "Move Terminal into New Window"), + original: 'Move Terminal into New Window', + }, moveToTerminalPanel: { value: localize('workbench.action.terminal.moveToTerminalPanel', "Move Terminal into Panel"), original: 'Move Terminal into Panel' From 651391e5ade73cf7c4655495e6b529351153f914 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 20 Dec 2023 22:12:08 +0100 Subject: [PATCH 04/26] Aux window: fullscreen windows show custom title bar (fix #201297) (#201332) --- .../electron-main/auxiliaryWindows.ts | 1 + .../auxiliaryWindowsMainService.ts | 5 +++ src/vs/platform/native/common/native.ts | 2 ++ .../electron-main/nativeHostMainService.ts | 5 +++ .../platform/window/electron-main/window.ts | 2 ++ .../windows/electron-main/windowImpl.ts | 12 +++++-- .../platform/windows/electron-main/windows.ts | 1 + .../electron-main/windowsMainService.ts | 5 +++ .../test/electron-main/windowsFinder.test.ts | 2 ++ .../parts/editor/auxiliaryEditorPart.ts | 32 +++++++++++++++---- src/vs/workbench/browser/window.ts | 17 +++++----- .../host/browser/browserHostService.ts | 23 ++++++++++++- .../workbench/services/host/browser/host.ts | 6 ++++ .../electron-sandbox/nativeHostService.ts | 2 ++ .../test/browser/workbenchTestServices.ts | 2 ++ .../electron-sandbox/workbenchTestServices.ts | 1 + 16 files changed, 100 insertions(+), 18 deletions(-) diff --git a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts index b2f471f5d7b..c67d3eac6fa 100644 --- a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts +++ b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts @@ -17,6 +17,7 @@ export interface IAuxiliaryWindowsMainService { readonly onDidMaximizeWindow: Event; readonly onDidUnmaximizeWindow: Event; + readonly onDidChangeFullScreen: Event; readonly onDidTriggerSystemContextMenu: Event<{ readonly window: IAuxiliaryWindow; readonly x: number; readonly y: number }>; createWindow(): BrowserWindowConstructorOptions; diff --git a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts index 949aba35af8..f87d8078a26 100644 --- a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts +++ b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts @@ -24,6 +24,9 @@ export class AuxiliaryWindowsMainService extends Disposable implements IAuxiliar private readonly _onDidUnmaximizeWindow = this._register(new Emitter()); readonly onDidUnmaximizeWindow = this._onDidUnmaximizeWindow.event; + private readonly _onDidChangeFullScreen = this._register(new Emitter()); + readonly onDidChangeFullScreen = this._onDidChangeFullScreen.event; + private readonly _onDidTriggerSystemContextMenu = this._register(new Emitter<{ window: IAuxiliaryWindow; x: number; y: number }>()); readonly onDidTriggerSystemContextMenu = this._onDidTriggerSystemContextMenu.event; @@ -85,6 +88,8 @@ export class AuxiliaryWindowsMainService extends Disposable implements IAuxiliar disposables.add(auxiliaryWindow.onDidMaximize(() => this._onDidMaximizeWindow.fire(auxiliaryWindow))); disposables.add(auxiliaryWindow.onDidUnmaximize(() => this._onDidUnmaximizeWindow.fire(auxiliaryWindow))); + disposables.add(auxiliaryWindow.onDidEnterFullScreen(() => this._onDidChangeFullScreen.fire(auxiliaryWindow))); + disposables.add(auxiliaryWindow.onDidLeaveFullScreen(() => this._onDidChangeFullScreen.fire(auxiliaryWindow))); disposables.add(auxiliaryWindow.onDidTriggerSystemContextMenu(({ x, y }) => this._onDidTriggerSystemContextMenu.fire({ window: auxiliaryWindow, x, y }))); Event.once(auxiliaryWindow.onDidClose)(() => disposables.dispose()); diff --git a/src/vs/platform/native/common/native.ts b/src/vs/platform/native/common/native.ts index a2b150c00ba..106505e908a 100644 --- a/src/vs/platform/native/common/native.ts +++ b/src/vs/platform/native/common/native.ts @@ -53,6 +53,8 @@ export interface ICommonNativeHostService { readonly onDidFocusMainWindow: Event; readonly onDidBlurMainWindow: Event; + readonly onDidChangeWindowFullScreen: Event; + readonly onDidFocusMainOrAuxiliaryWindow: Event; readonly onDidBlurMainOrAuxiliaryWindow: Event; diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index 79c912875bb..28e8d65665a 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -92,6 +92,11 @@ export class NativeHostMainService extends Disposable implements INativeHostMain Event.filter(Event.map(this.auxiliaryWindowsMainService.onDidUnmaximizeWindow, window => window.id), windowId => !!this.auxiliaryWindowsMainService.getWindowById(windowId)) ); + readonly onDidChangeWindowFullScreen = Event.any( + Event.map(this.windowsMainService.onDidChangeFullScreen, window => window.id), + Event.map(this.auxiliaryWindowsMainService.onDidChangeFullScreen, window => window.id) + ); + readonly onDidBlurMainWindow = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-blur', (event, window: BrowserWindow) => window.id), windowId => !!this.windowsMainService.getWindowById(windowId)); readonly onDidFocusMainWindow = Event.any( Event.map(Event.filter(Event.map(this.windowsMainService.onDidChangeWindowsCount, () => this.windowsMainService.getLastActiveWindow()), window => !!window), window => window!.id), diff --git a/src/vs/platform/window/electron-main/window.ts b/src/vs/platform/window/electron-main/window.ts index 653a4d23473..dbcef949cd8 100644 --- a/src/vs/platform/window/electron-main/window.ts +++ b/src/vs/platform/window/electron-main/window.ts @@ -18,6 +18,8 @@ export interface IBaseWindow extends IDisposable { readonly onDidMaximize: Event; readonly onDidUnmaximize: Event; readonly onDidTriggerSystemContextMenu: Event<{ readonly x: number; readonly y: number }>; + readonly onDidEnterFullScreen: Event; + readonly onDidLeaveFullScreen: Event; readonly onDidClose: Event; readonly id: number; diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index 36f9a65833f..4972933c581 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -98,6 +98,12 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { private readonly _onDidTriggerSystemContextMenu = this._register(new Emitter<{ x: number; y: number }>()); readonly onDidTriggerSystemContextMenu = this._onDidTriggerSystemContextMenu.event; + private readonly _onDidEnterFullScreen = this._register(new Emitter()); + readonly onDidEnterFullScreen = this._onDidEnterFullScreen.event; + + private readonly _onDidLeaveFullScreen = this._register(new Emitter()); + readonly onDidLeaveFullScreen = this._onDidLeaveFullScreen.event; + //#endregion abstract readonly id: number; @@ -121,6 +127,8 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { this._register(Event.fromNodeEventEmitter(win, 'focus')(() => { this._lastFocusTime = Date.now(); })); + this._register(Event.fromNodeEventEmitter(this._win, 'enter-full-screen')(() => this._onDidEnterFullScreen.fire())); + this._register(Event.fromNodeEventEmitter(this._win, 'leave-full-screen')(() => this._onDidLeaveFullScreen.fire())); // Sheet Offsets const useCustomTitleStyle = getTitleBarStyle(this.configurationService) === 'custom'; @@ -670,14 +678,14 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { })); // Window Fullscreen - this._register(Event.fromNodeEventEmitter(this._win, 'enter-full-screen')(() => { + this._register(this.onDidEnterFullScreen(() => { this.sendWhenReady('vscode:enterFullScreen', CancellationToken.None); this.joinNativeFullScreenTransition?.complete(); this.joinNativeFullScreenTransition = undefined; })); - this._register(Event.fromNodeEventEmitter(this._win, 'leave-full-screen')(() => { + this._register(this.onDidLeaveFullScreen(() => { this.sendWhenReady('vscode:leaveFullScreen', CancellationToken.None); this.joinNativeFullScreenTransition?.complete(); diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index d957eb1a039..1bfd4d3631f 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -32,6 +32,7 @@ export interface IWindowsMainService { readonly onDidSignalReadyWindow: Event; readonly onDidMaximizeWindow: Event; readonly onDidUnmaximizeWindow: Event; + readonly onDidChangeFullScreen: Event; readonly onDidTriggerSystemContextMenu: Event<{ readonly window: ICodeWindow; readonly x: number; readonly y: number }>; readonly onDidDestroyWindow: Event; diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 3053b0782ee..5a1473a0844 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -198,6 +198,9 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic private readonly _onDidUnmaximizeWindow = this._register(new Emitter()); readonly onDidUnmaximizeWindow = this._onDidUnmaximizeWindow.event; + private readonly _onDidChangeFullScreen = this._register(new Emitter()); + readonly onDidChangeFullScreen = this._onDidChangeFullScreen.event; + private readonly _onDidTriggerSystemContextMenu = this._register(new Emitter<{ window: ICodeWindow; x: number; y: number }>()); readonly onDidTriggerSystemContextMenu = this._onDidTriggerSystemContextMenu.event; @@ -1498,6 +1501,8 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic disposables.add(Event.once(createdWindow.onDidDestroy)(() => this.onWindowDestroyed(createdWindow))); disposables.add(createdWindow.onDidMaximize(() => this._onDidMaximizeWindow.fire(createdWindow))); disposables.add(createdWindow.onDidUnmaximize(() => this._onDidUnmaximizeWindow.fire(createdWindow))); + disposables.add(createdWindow.onDidEnterFullScreen(() => this._onDidChangeFullScreen.fire(createdWindow))); + disposables.add(createdWindow.onDidLeaveFullScreen(() => this._onDidChangeFullScreen.fire(createdWindow))); disposables.add(createdWindow.onDidTriggerSystemContextMenu(({ x, y }) => this._onDidTriggerSystemContextMenu.fire({ window: createdWindow, x, y }))); const webContents = assertIsDefined(createdWindow.win?.webContents); diff --git a/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts b/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts index 8b5784209eb..d395dc44ab1 100644 --- a/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts +++ b/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts @@ -40,6 +40,8 @@ suite('WindowsFinder', () => { onDidSignalReady: Event = Event.None; onDidClose: Event = Event.None; onDidDestroy: Event = Event.None; + onDidEnterFullScreen: Event = Event.None; + onDidLeaveFullScreen: Event = Event.None; whenClosedOrLoaded: Promise = Promise.resolve(); id: number = -1; win: Electron.BrowserWindow = null!; diff --git a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts index 72b84741e46..8d1ea43513e 100644 --- a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts +++ b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { hide, show } from 'vs/base/browser/dom'; +import { detectFullscreen, hide, show } from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { isNative } from 'vs/base/common/platform'; @@ -39,7 +39,8 @@ export class AuxiliaryEditorPart { @IConfigurationService private readonly configurationService: IConfigurationService, @IStatusbarService private readonly statusbarService: IStatusbarService, @ITitleService private readonly titleService: ITitleService, - @IEditorService private readonly editorService: IEditorService + @IEditorService private readonly editorService: IEditorService, + @IHostService private readonly hostService: IHostService ) { } @@ -52,7 +53,7 @@ export class AuxiliaryEditorPart { editorPartHeightOffset += statusbarPart.height; } - if (titlebarPart) { + if (titlebarPart && titlebarPartVisible) { editorPartHeightOffset += titlebarPart.height; } @@ -95,10 +96,31 @@ export class AuxiliaryEditorPart { // Titlebar let titlebarPart: IAuxiliaryTitlebarPart | undefined = undefined; + let titlebarPartVisible = false; const useCustomTitle = isNative && getTitleBarStyle(this.configurationService) === 'custom'; // custom title in aux windows only enabled in native if (useCustomTitle) { titlebarPart = disposables.add(this.titleService.createAuxiliaryTitlebarPart(auxiliaryWindow.container, editorPart)); + titlebarPartVisible = true; + disposables.add(titlebarPart.onDidChange(() => updateEditorPartHeight(true))); + + disposables.add(this.hostService.onDidChangeFullScreen(windowId => { + if (windowId !== auxiliaryWindow.window.vscodeWindowId) { + return; // ignore all but our window + } + + // Make sure to hide the custom title when we enter + // fullscren mode and show it when we lave it. + + const fullscreen = detectFullscreen(auxiliaryWindow.window); + const oldTitlebarPartVisible = titlebarPartVisible; + titlebarPartVisible = !fullscreen; + if (titlebarPart && oldTitlebarPartVisible !== titlebarPartVisible) { + titlebarPart.container.style.display = titlebarPartVisible ? '' : 'none'; + + updateEditorPartHeight(true); + } + })); } else { disposables.add(this.instantiationService.createInstance(WindowTitle, auxiliaryWindow.window, editorPart)); } @@ -132,9 +154,7 @@ export class AuxiliaryEditorPart { // Layout disposables.add(auxiliaryWindow.onDidLayout(dimension => { const titlebarPartHeight = titlebarPart?.height ?? 0; - if (titlebarPart) { - titlebarPart.layout(dimension.width, titlebarPartHeight, 0, 0); - } + titlebarPart?.layout(dimension.width, titlebarPartHeight, 0, 0); const editorPartHeight = dimension.height - computeEditorPartHeightOffset(); editorPart.layout(dimension.width, editorPartHeight, titlebarPartHeight, 0); diff --git a/src/vs/workbench/browser/window.ts b/src/vs/workbench/browser/window.ts index c2e85c276ea..56278f29851 100644 --- a/src/vs/workbench/browser/window.ts +++ b/src/vs/workbench/browser/window.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { isSafari, setFullscreen } from 'vs/base/browser/browser'; -import { addDisposableListener, addDisposableThrottledListener, detectFullscreen, EventHelper, EventType, getActiveWindow, getWindow, getWindows, getWindowsCount, windowOpenNoOpener, windowOpenPopup, windowOpenWithSuccess } from 'vs/base/browser/dom'; +import { addDisposableListener, detectFullscreen, EventHelper, EventType, getActiveWindow, getWindow, getWindows, getWindowsCount, windowOpenNoOpener, windowOpenPopup, windowOpenWithSuccess } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { HidDeviceData, requestHidDevice, requestSerialPort, requestUsbDevice, SerialPortData, UsbDeviceData } from 'vs/base/browser/deviceAccess'; import { timeout } from 'vs/base/common/async'; import { Event } from 'vs/base/common/event'; import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { matchesScheme, Schemas } from 'vs/base/common/network'; -import { isIOS, isMacintosh } from 'vs/base/common/platform'; +import { isIOS } from 'vs/base/common/platform'; import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; @@ -174,15 +174,14 @@ export class BrowserWindow extends BaseWindow { // Prevent default navigation on drop this._register(addDisposableListener(this.layoutService.mainContainer, EventType.DROP, e => EventHelper.stop(e, true))); - // Fullscreen (Browser) - for (const event of [EventType.FULLSCREEN_CHANGE, EventType.WK_FULLSCREEN_CHANGE]) { - this._register(addDisposableListener(mainWindow.document, event, () => setFullscreen(!!detectFullscreen(mainWindow)))); - } + // Fullscreen + this._register(this.hostService.onDidChangeFullScreen(windowId => { + if (windowId !== mainWindow.vscodeWindowId) { + return; // ignore all but main window + } - // Fullscreen (Native) - this._register(addDisposableThrottledListener(viewport, EventType.RESIZE, () => { setFullscreen(!!detectFullscreen(mainWindow)); - }, undefined, isMacintosh ? 2000 /* adjust for macOS animation */ : 800 /* can be throttled */)); + })); } private onWillShutdown(): void { diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index f2bf343ca9d..982ba91d8ce 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -15,7 +15,7 @@ import { whenEditorClosed } from 'vs/workbench/browser/editor'; import { IWorkspace, IWorkspaceProvider } from 'vs/workbench/browser/web.api'; import { IFileService } from 'vs/platform/files/common/files'; import { ILabelService, Verbosity } from 'vs/platform/label/common/label'; -import { ModifierKeyEmitter, disposableWindowInterval, getActiveDocument, getWindowId, onDidRegisterWindow, trackFocus } from 'vs/base/browser/dom'; +import { EventType, ModifierKeyEmitter, addDisposableListener, addDisposableThrottledListener, disposableWindowInterval, getActiveDocument, getWindowId, onDidRegisterWindow, trackFocus } from 'vs/base/browser/dom'; import { Disposable } from 'vs/base/common/lifecycle'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { memoize } from 'vs/base/common/decorators'; @@ -39,6 +39,7 @@ import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { coalesce } from 'vs/base/common/arrays'; import { mainWindow, isAuxiliaryWindow } from 'vs/base/browser/window'; +import { isIOS, isMacintosh } from 'vs/base/common/platform'; enum HostShutdownReason { @@ -204,6 +205,26 @@ export class BrowserHostService extends Disposable implements IHostService { return Event.latch(emitter.event, undefined, this._store); } + @memoize + get onDidChangeFullScreen(): Event { + const emitter = this._register(new Emitter()); + + this._register(Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposables }) => { + const windowId = getWindowId(window); + const viewport = isIOS && window.visualViewport ? window.visualViewport /** Visual viewport */ : window /** Layout viewport */; + + // Fullscreen (Browser) + for (const event of [EventType.FULLSCREEN_CHANGE, EventType.WK_FULLSCREEN_CHANGE]) { + disposables.add(addDisposableListener(window.document, event, () => emitter.fire(windowId))); + } + + // Fullscreen (Native) + disposables.add(addDisposableThrottledListener(viewport, EventType.RESIZE, () => emitter.fire(windowId), undefined, isMacintosh ? 2000 /* adjust for macOS animation */ : 800 /* can be throttled */)); + }, { window: mainWindow, disposables: this._store })); + + return emitter.event; + } + openWindow(options?: IOpenEmptyWindowOptions): Promise; openWindow(toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise; openWindow(arg1?: IOpenEmptyWindowOptions | IWindowOpenable[], arg2?: IOpenWindowOptions): Promise { diff --git a/src/vs/workbench/services/host/browser/host.ts b/src/vs/workbench/services/host/browser/host.ts index a543bc9e6f4..02d7fcea201 100644 --- a/src/vs/workbench/services/host/browser/host.ts +++ b/src/vs/workbench/services/host/browser/host.ts @@ -65,6 +65,12 @@ export interface IHostService { */ readonly onDidChangeActiveWindow: Event; + /** + * Emitted when the window with the given identifier changes + * its fullscreen state. + */ + readonly onDidChangeFullScreen: Event; + /** * Opens an empty window. The optional parameter allows to define if * a new window should open or the existing one change to an empty. diff --git a/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts b/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts index 67ceabf6bc7..4cd56f09eda 100644 --- a/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts +++ b/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts @@ -94,6 +94,8 @@ class WorkbenchHostService extends Disposable implements IHostService { return Event.latch(emitter.event, undefined, this._store); } + readonly onDidChangeFullScreen = Event.filter(this.nativeHostService.onDidChangeWindowFullScreen, id => hasWindow(id), this._store); + openWindow(options?: IOpenEmptyWindowOptions): Promise; openWindow(toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise; openWindow(arg1?: IOpenEmptyWindowOptions | IWindowOpenable[], arg2?: IOpenWindowOptions): Promise { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 304f39d2bcc..ceabefed273 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -1492,6 +1492,8 @@ export class TestHostService implements IHostService { private _onDidChangeWindow = new Emitter(); readonly onDidChangeActiveWindow = this._onDidChangeWindow.event; + readonly onDidChangeFullScreen: Event = Event.None; + setFocus(focus: boolean) { this._hasFocus = focus; this._onDidChangeFocus.fire(this._hasFocus); diff --git a/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts b/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts index 68707cccf06..e992583f9d6 100644 --- a/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts @@ -75,6 +75,7 @@ export class TestNativeHostService implements INativeHostService { onDidChangeColorScheme = Event.None; onDidChangePassword = Event.None; onDidTriggerWindowSystemContextMenu: Event<{ windowId: number; x: number; y: number }> = Event.None; + onDidChangeWindowFullScreen = Event.None; onDidChangeDisplay = Event.None; windowCount = Promise.resolve(1); From b964ed995a627da7ad5934804f2e11ecd3bbd673 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 20 Dec 2023 14:09:46 -0800 Subject: [PATCH 05/26] eng: fix waitServer not working in unit test debug (#201334) Fixes #182341 --- test/unit/electron/index.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/unit/electron/index.js b/test/unit/electron/index.js index a02bce4999c..f61514f72f1 100644 --- a/test/unit/electron/index.js +++ b/test/unit/electron/index.js @@ -33,7 +33,7 @@ const minimist = require('minimist'); * dev: boolean; * reporter: string; * 'reporter-options': string; - * 'wait-server': string; + * 'waitServer': string; * timeout: string; * 'crash-reporter-directory': string; * tfs: string; @@ -43,7 +43,7 @@ const minimist = require('minimist'); * }} */ const args = minimist(process.argv.slice(2), { - string: ['grep', 'run', 'runGlob', 'reporter', 'reporter-options', 'wait-server', 'timeout', 'crash-reporter-directory', 'tfs'], + string: ['grep', 'run', 'runGlob', 'reporter', 'reporter-options', 'waitServer', 'timeout', 'crash-reporter-directory', 'tfs'], boolean: ['build', 'coverage', 'help', 'dev'], alias: { 'grep': ['g', 'f'], @@ -69,7 +69,7 @@ Options: --dev, --dev-tools, --devTools open dev tools, keep window open, reuse app data --reporter the mocha reporter (default: "spec") --reporter-options the mocha reporter options (default: "") ---wait-server port to connect to and wait before running tests +--waitServer port to connect to and wait before running tests --timeout timeout for tests --crash-reporter-directory crash reporter directory --tfs TFS server URL @@ -232,8 +232,8 @@ app.on('ready', () => { win.webContents.openDevTools(); } - if (args['wait-server']) { - waitForServer(Number(args['wait-server'])).then(sendRun); + if (args.waitServer) { + waitForServer(Number(args.waitServer)).then(sendRun); } else { sendRun(); } From 468f6fca77ffbb4f951a9350ff883c202a8c9be2 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Wed, 20 Dec 2023 15:13:18 -0800 Subject: [PATCH 06/26] Don't use disposable Action and use IAction instead (#201337) Turns out all of these disposables were not getting cleaned up... but they really don't need to be disposables in this context. ref #201320 --- .../platform/quickinput/browser/quickInput.ts | 35 +++++++++---------- .../quickinput/browser/quickInputList.ts | 34 +++++------------- .../quickinput/browser/quickInputUtils.ts | 20 ++++++++++- 3 files changed, 44 insertions(+), 45 deletions(-) diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 30b69b66f99..3584e6e6d7b 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -15,7 +15,6 @@ import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/lis import { IListOptions, IListStyles, List } from 'vs/base/browser/ui/list/listWidget'; import { IProgressBarStyles, ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { IToggleStyles, Toggle } from 'vs/base/browser/ui/toggle/toggle'; -import { Action } from 'vs/base/common/actions'; import { equals } from 'vs/base/common/arrays'; import { TimeoutTimer } from 'vs/base/common/async'; import { Codicon } from 'vs/base/common/codicons'; @@ -30,7 +29,7 @@ import { localize } from 'vs/nls'; import { IInputBox, IKeyMods, IQuickInput, IQuickInputButton, IQuickInputHideEvent, IQuickInputToggle, IQuickNavigateConfiguration, IQuickPick, IQuickPickDidAcceptEvent, IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator, IQuickPickSeparatorButtonEvent, IQuickPickWillAcceptEvent, IQuickWidget, ItemActivation, NO_KEY_MODS, QuickInputHideReason } from 'vs/platform/quickinput/common/quickInput'; import { QuickInputBox } from './quickInputBox'; import { QuickInputList, QuickInputListFocus } from './quickInputList'; -import { getIconClass, renderQuickInputDescription } from './quickInputUtils'; +import { quickInputButtonToAction, renderQuickInputDescription } from './quickInputUtils'; export interface IQuickInputOptions { idPrefix: string; @@ -388,23 +387,23 @@ class QuickInput extends Disposable implements IQuickInput { if (this.buttonsUpdated) { this.buttonsUpdated = false; this.ui.leftActionBar.clear(); - const leftButtons = this.buttons.filter(button => button === backButton); - this.ui.leftActionBar.push(leftButtons.map((button, index) => { - const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, async () => { - this.onDidTriggerButtonEmitter.fire(button); - }); - action.tooltip = button.tooltip || ''; - return action; - }), { icon: true, label: false }); + const leftButtons = this.buttons + .filter(button => button === backButton) + .map((button, index) => quickInputButtonToAction( + button, + `id-${index}`, + async () => this.onDidTriggerButtonEmitter.fire(button) + )); + this.ui.leftActionBar.push(leftButtons, { icon: true, label: false }); this.ui.rightActionBar.clear(); - const rightButtons = this.buttons.filter(button => button !== backButton); - this.ui.rightActionBar.push(rightButtons.map((button, index) => { - const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, async () => { - this.onDidTriggerButtonEmitter.fire(button); - }); - action.tooltip = button.tooltip || ''; - return action; - }), { icon: true, label: false }); + const rightButtons = this.buttons + .filter(button => button !== backButton) + .map((button, index) => quickInputButtonToAction( + button, + `id-${index}`, + async () => this.onDidTriggerButtonEmitter.fire(button) + )); + this.ui.rightActionBar.push(rightButtons, { icon: true, label: false }); } if (this.togglesUpdated) { this.togglesUpdated = false; diff --git a/src/vs/platform/quickinput/browser/quickInputList.ts b/src/vs/platform/quickinput/browser/quickInputList.ts index fdbde66caa8..00ac9ac47ae 100644 --- a/src/vs/platform/quickinput/browser/quickInputList.ts +++ b/src/vs/platform/quickinput/browser/quickInputList.ts @@ -13,7 +13,6 @@ import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/ import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListAccessibilityProvider, IListOptions, IListStyles, List } from 'vs/base/browser/ui/list/listWidget'; -import { IAction } from 'vs/base/common/actions'; import { range } from 'vs/base/common/arrays'; import { ThrottledDelayer } from 'vs/base/common/async'; import { compareAnything } from 'vs/base/common/comparers'; @@ -30,7 +29,7 @@ import { ltrim } from 'vs/base/common/strings'; import 'vs/css!./media/quickInput'; import { localize } from 'vs/nls'; import { IQuickInputOptions } from 'vs/platform/quickinput/browser/quickInput'; -import { getIconClass } from 'vs/platform/quickinput/browser/quickInputUtils'; +import { quickInputButtonToAction } from 'vs/platform/quickinput/browser/quickInputUtils'; import { IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator, IQuickPickSeparatorButtonEvent, QuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { Lazy } from 'vs/base/common/lazy'; import { URI } from 'vs/base/common/uri'; @@ -355,30 +354,13 @@ class ListElementRenderer implements IListRenderer { - let cssClasses = button.iconClass || (button.iconPath ? getIconClass(button.iconPath) : undefined); - if (button.alwaysVisible) { - cssClasses = cssClasses ? `${cssClasses} always-visible` : 'always-visible'; - } - return { - id: `id-${index}`, - class: cssClasses, - enabled: true, - label: '', - tooltip: button.tooltip || '', - run: () => { - mainItem.type !== 'separator' - ? element.fireButtonTriggered({ - button, - item: mainItem - }) - : element.fireSeparatorButtonTriggered({ - button, - separator: mainItem - }); - } - }; - }), { icon: true, label: false }); + data.actionBar.push(buttons.map((button, index) => quickInputButtonToAction( + button, + `id-${index}`, + () => mainItem.type !== 'separator' + ? element.fireButtonTriggered({ button, item: mainItem }) + : element.fireSeparatorButtonTriggered({ button, separator: mainItem }) + )), { icon: true, label: false }); data.entry.classList.add('has-actions'); } else { data.entry.classList.remove('has-actions'); diff --git a/src/vs/platform/quickinput/browser/quickInputUtils.ts b/src/vs/platform/quickinput/browser/quickInputUtils.ts index 1c9566a6622..cfa7e231a23 100644 --- a/src/vs/platform/quickinput/browser/quickInputUtils.ts +++ b/src/vs/platform/quickinput/browser/quickInputUtils.ts @@ -16,11 +16,13 @@ import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/quickInput'; import { localize } from 'vs/nls'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IQuickInputButton } from 'vs/platform/quickinput/common/quickInput'; +import { IAction } from 'vs/base/common/actions'; const iconPathToClass: Record = {}; const iconClassGenerator = new IdGenerator('quick-input-button-icon-'); -export function getIconClass(iconPath: { dark: URI; light?: URI } | undefined): string | undefined { +function getIconClass(iconPath: { dark: URI; light?: URI } | undefined): string | undefined { if (!iconPath) { return undefined; } @@ -39,6 +41,22 @@ export function getIconClass(iconPath: { dark: URI; light?: URI } | undefined): return iconClass; } +export function quickInputButtonToAction(button: IQuickInputButton, id: string, run: () => unknown): IAction { + let cssClasses = button.iconClass || getIconClass(button.iconPath); + if (button.alwaysVisible) { + cssClasses = cssClasses ? `${cssClasses} always-visible` : 'always-visible'; + } + + return { + id, + label: '', + tooltip: button.tooltip || '', + class: cssClasses, + enabled: true, + run + }; +} + export function renderQuickInputDescription(description: string, container: HTMLElement, actionHandler: { callback: (content: string) => void; disposables: DisposableStore }) { dom.reset(container); const parsed = parseLinkedText(description); From c60da32708d1c63db3fc309fb940007d975ac34a Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Wed, 20 Dec 2023 16:03:44 -0800 Subject: [PATCH 07/26] Use the hover delegate in more UI components of Quick Pick (#201339) This way we have a consistent hover UI --- .../quickinput/browser/quickInputList.ts | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/quickinput/browser/quickInputList.ts b/src/vs/platform/quickinput/browser/quickInputList.ts index 00ac9ac47ae..a1ad53c81c9 100644 --- a/src/vs/platform/quickinput/browser/quickInputList.ts +++ b/src/vs/platform/quickinput/browser/quickInputList.ts @@ -8,7 +8,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { AriaRole } from 'vs/base/browser/ui/aria/aria'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; -import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate, IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; @@ -234,7 +234,10 @@ class ListElementRenderer implements IListRendererdom.prepend(data.label.element, $('.quick-input-list-icon')); @@ -276,14 +279,14 @@ class ListElementRenderer implements IListRenderer { // always prefer item over separator because if item is defined, it must be the main item type From 8a45b1be239c807d4635fbfc56058ee99e0a30ed Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Thu, 21 Dec 2023 01:15:34 +0100 Subject: [PATCH 08/26] fix: memory leak in menubar (#198052) * fix: memory leak in menubar * Update src/vs/base/browser/ui/menu/menubar.ts Co-authored-by: Benjamin Pasero * Update src/vs/base/browser/ui/menu/menubar.ts Co-authored-by: Benjamin Pasero * Update src/vs/base/browser/ui/menu/menubar.ts Co-authored-by: Benjamin Pasero * fix: potential memory leak when focusing another menu --------- Co-authored-by: Benjamin Pasero Co-authored-by: SteVen Batten --- src/vs/base/browser/ui/menu/menubar.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 5c6ff94f205..2e58738bdab 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -17,7 +17,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode, KeyMod, ScanCode, ScanCodeUtils } from 'vs/base/common/keyCodes'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; -import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { isMacintosh } from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import 'vs/css!./menubar'; @@ -87,6 +87,8 @@ export class MenuBar extends Disposable { private numMenusShown: number = 0; private overflowLayoutScheduled: IDisposable | undefined = undefined; + private readonly menuDisposables = this._register(new DisposableStore()); + constructor(private container: HTMLElement, private options: IMenuBarOptions, private menuStyle: IMenuStyles) { super(); @@ -751,6 +753,7 @@ export class MenuBar extends Disposable { } if (this.focusedMenu) { + this.cleanupCustomMenu(); this.showCustomMenu(this.focusedMenu.index, this.openedViaKeyboard); } break; @@ -985,6 +988,7 @@ export class MenuBar extends Disposable { this.focusedMenu = { index: this.focusedMenu.index }; } + this.menuDisposables.clear(); } private showCustomMenu(menuIndex: number, selectFirst = true): void { @@ -1025,9 +1029,8 @@ export class MenuBar extends Disposable { useEventAsContext: true }; - const menuWidget = this._register(new Menu(menuHolder, customMenu.actions, menuOptions, this.menuStyle)); - - this._register(menuWidget.onDidCancel(() => { + const menuWidget = this.menuDisposables.add(new Menu(menuHolder, customMenu.actions, menuOptions, this.menuStyle)); + this.menuDisposables.add(menuWidget.onDidCancel(() => { this.focusState = MenubarState.FOCUSED; })); From 30b777312745e84972956d4361465d4d38aa0f78 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 21 Dec 2023 02:14:13 +0100 Subject: [PATCH 09/26] layout - show keybindings in picker (fix #143669) (#199512) --- src/vs/workbench/browser/actions/layoutActions.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index fd2348aabcf..a3bacf4ad5c 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -28,6 +28,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { ICommandActionTitle } from 'vs/platform/action/common/action'; import { mainWindow } from 'vs/base/browser/window'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; // Register Icons const menubarIcon = registerIcon('menuBar', Codicon.layoutMenubar, localize('menuBarIcon', "Represents the menu bar")); @@ -1422,7 +1423,7 @@ registerAction2(class CustomizeLayoutAction extends Action2 { }); } - getItems(contextKeyService: IContextKeyService): QuickPickItem[] { + getItems(contextKeyService: IContextKeyService, keybindingService: IKeybindingService): QuickPickItem[] { const toQuickPickItem = (item: CustomizeLayoutItem): IQuickPickItem => { const toggled = item.active.evaluate(contextKeyService.getContext(null)); let label = item.useButtons ? @@ -1448,6 +1449,7 @@ registerAction2(class CustomizeLayoutAction extends Action2 { id: item.id, label, ariaLabel, + keybinding: keybindingService.lookupKeybinding(item.id, contextKeyService), buttons: !item.useButtons ? undefined : [ { alwaysVisible: false, @@ -1491,10 +1493,11 @@ registerAction2(class CustomizeLayoutAction extends Action2 { const contextKeyService = accessor.get(IContextKeyService); const commandService = accessor.get(ICommandService); const quickInputService = accessor.get(IQuickInputService); + const keybindingService = accessor.get(IKeybindingService); const quickPick = quickInputService.createQuickPick(); this._currentQuickPick = quickPick; - quickPick.items = this.getItems(contextKeyService); + quickPick.items = this.getItems(contextKeyService, keybindingService); quickPick.ignoreFocusOut = true; quickPick.hideInput = true; quickPick.title = localize('customizeLayoutQuickPickTitle', "Customize Layout"); @@ -1520,7 +1523,7 @@ registerAction2(class CustomizeLayoutAction extends Action2 { let selectedItem: CustomizeLayoutItem | undefined = undefined; disposables.add(contextKeyService.onDidChangeContext(changeEvent => { if (changeEvent.affectsSome(LayoutContextKeySet)) { - quickPick.items = this.getItems(contextKeyService); + quickPick.items = this.getItems(contextKeyService, keybindingService); if (selectedItem) { quickPick.activeItems = quickPick.items.filter(item => (item as CustomizeLayoutItem).id === selectedItem?.id) as IQuickPickItem[]; } From 58041888f48dc35ab6f4885765b7a45d1d67fe8e Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Fri, 22 Dec 2023 16:19:04 +0100 Subject: [PATCH 10/26] Copy width & height from the ResizeObserver callback (#201406) Copy width & height from the ResizeObserver callback to avoid "ResizeObserver loop completed with undelivered notifications" error (fixes microsoft/monaco-editor#4311) --- src/vs/editor/browser/config/elementSizeObserver.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/browser/config/elementSizeObserver.ts b/src/vs/editor/browser/config/elementSizeObserver.ts index 9dd5af8e6d3..33eefc3aba8 100644 --- a/src/vs/editor/browser/config/elementSizeObserver.ts +++ b/src/vs/editor/browser/config/elementSizeObserver.ts @@ -47,10 +47,10 @@ export class ElementSizeObserver extends Disposable { // Otherwise we will postpone to the next animation frame. // We'll use `observeContentRect` to store the content rect we received. - let observeContentRect: DOMRectReadOnly | null = null; + let observedDimenstion: IDimension | null = null; const observeNow = () => { - if (observeContentRect) { - this.observe({ width: observeContentRect.width, height: observeContentRect.height }); + if (observedDimenstion) { + this.observe({ width: observedDimenstion.width, height: observedDimenstion.height }); } else { this.observe(); } @@ -75,7 +75,11 @@ export class ElementSizeObserver extends Disposable { }; this._resizeObserver = new ResizeObserver((entries) => { - observeContentRect = (entries && entries[0] && entries[0].contentRect ? entries[0].contentRect : null); + if (entries && entries[0] && entries[0].contentRect) { + observedDimenstion = { width: entries[0].contentRect.width, height: entries[0].contentRect.height }; + } else { + observedDimenstion = null; + } shouldObserve = true; update(); }); From 860d67064a9c1ef8ce0c8de35a78bea01033f76c Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 22 Dec 2023 22:59:54 +0100 Subject: [PATCH 11/26] Git - refactor ref pickers (#201411) * Refactored quick picks * Another set of refactoring * Moved over the remaining classes * git.checkoutType setting should only be used by the Checkout quick pick * Introduce RefProcessor and cleanup code * Introduced multiple processor types * Another round of refactoring --- extensions/git/package.nls.json | 6 +- extensions/git/src/commands.ts | 558 ++++++++++++++++++++------------ 2 files changed, 351 insertions(+), 213 deletions(-) diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index dc59c3342d7..9b053671d0f 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -64,12 +64,12 @@ "command.deleteBranch": "Delete Branch...", "command.renameBranch": "Rename Branch...", "command.cherryPick": "Cherry Pick...", - "command.merge": "Merge Branch...", + "command.merge": "Merge...", "command.mergeAbort": "Abort Merge", "command.rebase": "Rebase Branch...", "command.createTag": "Create Tag", - "command.deleteTag": "Delete Tag", - "command.deleteRemoteTag": "Delete Remote Tag", + "command.deleteTag": "Delete Tag...", + "command.deleteRemoteTag": "Delete Remote Tag...", "command.fetch": "Fetch", "command.fetchPrune": "Fetch (Prune)", "command.fetchAll": "Fetch From All Remotes", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 110c2e20bc6..50a9fbbb22f 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -8,7 +8,7 @@ import * as path from 'path'; import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator'; -import { Branch, ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote } from './api/git'; +import { ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote } from './api/git'; import { Git, Stash } from './git'; import { Model } from './model'; import { Repository, Resource, ResourceGroupType } from './repository'; @@ -20,120 +20,179 @@ import { ApiRepository } from './api/api1'; import { getRemoteSourceActions, pickRemoteSource } from './remoteSource'; import { RemoteSourceAction } from './api/git-base'; -class CheckoutItem implements QuickPickItem { +abstract class CheckoutCommandItem implements QuickPickItem { + abstract get label(): string; + get description(): string { return ''; } + get alwaysShow(): boolean { return true; } +} + +class CreateBranchItem extends CheckoutCommandItem { + get label(): string { return l10n.t('{0} Create new branch...', '$(plus)'); } +} + +class CreateBranchFromItem extends CheckoutCommandItem { + get label(): string { return l10n.t('{0} Create new branch from...', '$(plus)'); } +} + +class CheckoutDetachedItem extends CheckoutCommandItem { + get label(): string { return l10n.t('{0} Checkout detached...', '$(debug-disconnect)'); } +} + +class RefItemSeparator implements QuickPickItem { + get kind(): QuickPickItemKind { return QuickPickItemKind.Separator; } + + get label(): string { + switch (this.refType) { + case RefType.Head: + return l10n.t('branches'); + case RefType.RemoteHead: + return l10n.t('remote branches'); + case RefType.Tag: + return l10n.t('tags'); + default: + return ''; + } + } + + constructor(private readonly refType: RefType) { } +} + +class RefItem implements QuickPickItem { + + get label(): string { + switch (this.ref.type) { + case RefType.Head: + return `$(git-branch) ${this.ref.name ?? this.shortCommit}`; + case RefType.RemoteHead: + return `$(cloud) ${this.ref.name ?? this.shortCommit}`; + case RefType.Tag: + return `$(tag) ${this.ref.name ?? this.shortCommit}`; + default: + return ''; + } + } + + get description(): string { + switch (this.ref.type) { + case RefType.Head: + return this.shortCommit; + case RefType.RemoteHead: + return l10n.t('Remote branch at {0}', this.shortCommit); + case RefType.Tag: + return l10n.t('Tag at {0}', this.shortCommit); + default: + return ''; + } + } - protected get shortCommit(): string { return (this.ref.commit || '').substr(0, 8); } - get label(): string { return `${this.repository.isBranchProtected(this.ref) ? '$(lock)' : '$(git-branch)'} ${this.ref.name || this.shortCommit}`; } - get description(): string { return this.shortCommit; } get refName(): string | undefined { return this.ref.name; } get refRemote(): string | undefined { return this.ref.remote; } + get shortCommit(): string { return (this.ref.commit || '').substr(0, 8); } + + private _buttons?: QuickInputButton[]; get buttons(): QuickInputButton[] | undefined { return this._buttons; } set buttons(newButtons: QuickInputButton[] | undefined) { this._buttons = newButtons; } - constructor(protected repository: Repository, protected ref: Ref, protected _buttons?: QuickInputButton[]) { } + constructor(protected readonly ref: Ref) { } +} - async run(opts?: { detached?: boolean }): Promise { +class CheckoutItem extends RefItem { + + async run(repository: Repository, opts?: { detached?: boolean }): Promise { if (!this.ref.name) { return; } - const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const config = workspace.getConfiguration('git', Uri.file(repository.root)); const pullBeforeCheckout = config.get('pullBeforeCheckout', false) === true; const treeish = opts?.detached ? this.ref.commit ?? this.ref.name : this.ref.name; - await this.repository.checkout(treeish, { ...opts, pullBeforeCheckout }); + await repository.checkout(treeish, { ...opts, pullBeforeCheckout }); } } -class CheckoutTagItem extends CheckoutItem { +class CheckoutProtectedItem extends CheckoutItem { - override get label(): string { return `$(tag) ${this.ref.name || this.shortCommit}`; } - override get description(): string { - return l10n.t('Tag at {0}', this.shortCommit); + override get label(): string { + return `$(lock) ${this.ref.name ?? this.shortCommit}`; } - override async run(opts?: { detached?: boolean }): Promise { - if (!this.ref.name) { - return; - } - - await this.repository.checkout(this.ref.name, opts); - } } -class CheckoutRemoteHeadItem extends CheckoutItem { +class CheckoutRemoteHeadItem extends RefItem { - override get label(): string { return `$(cloud) ${this.ref.name || this.shortCommit}`; } - override get description(): string { - return l10n.t('Remote branch at {0}', this.shortCommit); - } - - override async run(opts?: { detached?: boolean }): Promise { + async run(repository: Repository, opts?: { detached?: boolean }): Promise { if (!this.ref.name) { return; } if (opts?.detached) { - await this.repository.checkout(this.ref.commit ?? this.ref.name, opts); + await repository.checkout(this.ref.commit ?? this.ref.name, opts); return; } - const branches = await this.repository.findTrackingBranches(this.ref.name); + const branches = await repository.findTrackingBranches(this.ref.name); if (branches.length > 0) { - await this.repository.checkout(branches[0].name!, opts); + await repository.checkout(branches[0].name!, opts); } else { - await this.repository.checkoutTracking(this.ref.name, opts); + await repository.checkoutTracking(this.ref.name, opts); } } } -class BranchDeleteItem implements QuickPickItem { +class CheckoutTagItem extends RefItem { - private get shortCommit(): string { return (this.ref.commit || '').substr(0, 8); } - get branchName(): string | undefined { return this.ref.name; } - get label(): string { return this.branchName || ''; } - get description(): string { return this.shortCommit; } - - constructor(private ref: Ref) { } - - async run(repository: Repository, force?: boolean): Promise { - if (!this.branchName) { + async run(repository: Repository, opts?: { detached?: boolean }): Promise { + if (!this.ref.name) { return; } - await repository.deleteBranch(this.branchName, force); + + await repository.checkout(this.ref.name, opts); } } -class MergeItem implements QuickPickItem { +class BranchDeleteItem extends RefItem { - private shortCommit: string; - - get label(): string { - return this.ref.type === RefType.RemoteHead ? - `$(cloud) ${this.ref.name ?? this.shortCommit}` : - `${this.repository.isBranchProtected(this.ref) ? '$(lock)' : '$(git-branch)'} ${this.ref.name ?? this.shortCommit}`; - } - - get description(): string { - return this.ref.type === RefType.RemoteHead ? l10n.t('Remote branch at {0}', this.shortCommit) : this.shortCommit; - } - - constructor(private readonly repository: Repository, private readonly ref: Ref) { - this.shortCommit = (this.ref.commit ?? '').substring(0, 8); - } - - async run(): Promise { - await this.repository.merge(this.ref.name ?? this.ref.commit!); + async run(repository: Repository, force?: boolean): Promise { + if (this.ref.name) { + await repository.deleteBranch(this.ref.name, force); + } } } -class RebaseItem implements QuickPickItem { +class TagDeleteItem extends RefItem { - get label(): string { return this.ref.name || ''; } - description: string = ''; + async run(repository: Repository): Promise { + if (this.ref.name) { + await repository.deleteTag(this.ref.name); + } + } +} - constructor(readonly ref: Ref) { } +class RemoteTagDeleteItem extends RefItem { + + override get description(): string { + return l10n.t('Remote tag at {0}', this.shortCommit); + } + + async run(repository: Repository, remote: string): Promise { + if (this.ref.name) { + await repository.deleteRemoteTag(remote, this.ref.name); + } + } +} + +class MergeItem extends RefItem { + + async run(repository: Repository): Promise { + if (this.ref.name || this.ref.commit) { + await repository.merge(this.ref.name ?? this.ref.commit!); + } + } +} + +class RebaseItem extends RefItem { async run(repository: Repository): Promise { if (this.ref?.name) { @@ -142,22 +201,11 @@ class RebaseItem implements QuickPickItem { } } -class CreateBranchItem implements QuickPickItem { - get label(): string { return '$(plus) ' + l10n.t('Create new branch...'); } - get description(): string { return ''; } - get alwaysShow(): boolean { return true; } -} +class RebaseUpstreamItem extends RebaseItem { -class CreateBranchFromItem implements QuickPickItem { - get label(): string { return '$(plus) ' + l10n.t('Create new branch from...'); } - get description(): string { return ''; } - get alwaysShow(): boolean { return true; } -} - -class CheckoutDetachedItem implements QuickPickItem { - get label(): string { return '$(debug-disconnect) ' + l10n.t('Checkout detached...'); } - get description(): string { return ''; } - get alwaysShow(): boolean { return true; } + override get description(): string { + return '(upstream)'; + } } class HEADItem implements QuickPickItem { @@ -265,7 +313,7 @@ async function categorizeResourceByResolution(resources: Resource[]): Promise<{ return { merge, resolved, unresolved, deletionConflicts }; } -async function createCheckoutItems(repository: Repository, detached = false): Promise { +async function createCheckoutItems(repository: Repository, detached = false): Promise { const config = workspace.getConfiguration('git'); const checkoutTypeConfig = config.get('checkoutType'); let checkoutTypes: string[]; @@ -284,39 +332,13 @@ async function createCheckoutItems(repository: Repository, detached = false): Pr } const refs = await repository.getRefs(); - const processors = checkoutTypes.map(type => getCheckoutProcessor(repository, type)) - .filter(p => !!p) as CheckoutProcessor[]; - - for (const ref of refs) { - if (!detached && ref.name === 'origin/HEAD') { - continue; - } - - for (const processor of processors) { - processor.onRef(ref); - } - } + const refProcessors = checkoutTypes.map(type => getCheckoutRefProcessor(repository, type)) + .filter(p => !!p) as RefProcessor[]; const buttons = await getRemoteRefItemButtons(repository); - let fallbackRemoteButtons: RemoteSourceActionButton[] | undefined = []; - const remote = repository.remotes.find(r => r.pushUrl === repository.HEAD?.remote || r.fetchUrl === repository.HEAD?.remote) ?? repository.remotes[0]; - const remoteUrl = remote?.pushUrl ?? remote?.fetchUrl; - if (remoteUrl) { - fallbackRemoteButtons = buttons.get(remoteUrl); - } + const itemsProcessor = new CheckoutItemsProcessor(refProcessors, repository, buttons, detached); - return processors.reduce((r, p) => r.concat(...p.items.map((item) => { - if (item.refRemote) { - const matchingRemote = repository.remotes.find((remote) => remote.name === item.refRemote); - const remoteUrl = matchingRemote?.pushUrl ?? matchingRemote?.fetchUrl; - if (remoteUrl) { - item.buttons = buttons.get(item.refRemote); - } - } - - item.buttons = fallbackRemoteButtons; - return item; - })), []); + return itemsProcessor.processRefs(refs); } type RemoteSourceActionButton = { @@ -347,30 +369,181 @@ async function getRemoteRefItemButtons(repository: Repository) { return remoteUrlsToActions; } -class CheckoutProcessor { +class RefProcessor { + protected readonly refs: Ref[] = []; - private refs: Ref[] = []; - get items(): CheckoutItem[] { return this.refs.map(r => new this.ctor(this.repository, r)); } - constructor(private repository: Repository, private type: RefType, private ctor: { new(repository: Repository, ref: Ref): CheckoutItem }) { } + get items(): QuickPickItem[] { + const items = this.refs.map(r => new this.ctor(r)); + return items.length === 0 ? items : [new RefItemSeparator(this.type), ...items]; + } - onRef(ref: Ref): void { - if (ref.type === this.type) { - this.refs.push(ref); + constructor(protected readonly type: RefType, protected readonly ctor: { new(ref: Ref): QuickPickItem } = RefItem) { } + + processRef(ref: Ref): boolean { + if (!ref.name && !ref.commit) { + return false; } + if (ref.type !== this.type) { + return false; + } + + this.refs.push(ref); + return true; } } -function getCheckoutProcessor(repository: Repository, type: string): CheckoutProcessor | undefined { - switch (type) { - case 'local': - return new CheckoutProcessor(repository, RefType.Head, CheckoutItem); - case 'remote': - return new CheckoutProcessor(repository, RefType.RemoteHead, CheckoutRemoteHeadItem); - case 'tags': - return new CheckoutProcessor(repository, RefType.Tag, CheckoutTagItem); +class RefItemsProcessor { + + constructor(protected readonly processors: RefProcessor[]) { } + + processRefs(refs: Ref[]): QuickPickItem[] { + for (const ref of refs) { + for (const processor of this.processors) { + if (processor.processRef(ref)) { + break; + } + } + } + + const result: QuickPickItem[] = []; + for (const processor of this.processors) { + result.push(...processor.items); + } + + return result; + } +} + +class RebaseItemsProcessors extends RefItemsProcessor { + + private upstreamName: string | undefined; + + constructor(private readonly repository: Repository) { + super([ + new RefProcessor(RefType.Head, RebaseItem), + new RefProcessor(RefType.RemoteHead, RebaseItem) + ]); + + if (this.repository.HEAD?.upstream) { + this.upstreamName = `${this.repository.HEAD?.upstream.remote}/${this.repository.HEAD?.upstream.name}`; + } } - return undefined; + override processRefs(refs: Ref[]): QuickPickItem[] { + const result: QuickPickItem[] = []; + + for (const ref of refs) { + if (ref.name === this.repository.HEAD?.name) { + continue; + } + + if (ref.name === this.upstreamName) { + result.push(new RebaseUpstreamItem(ref)); + continue; + } + + for (const processor of this.processors) { + if (processor.processRef(ref)) { + break; + } + } + } + + for (const processor of this.processors) { + result.push(...processor.items); + } + + return result; + } +} + +class CheckoutRefProcessor extends RefProcessor { + + override get items(): QuickPickItem[] { + const items = this.refs.map(ref => { + return this.repository.isBranchProtected(ref) ? + new CheckoutProtectedItem(ref) : + new CheckoutItem(ref); + }); + + return items.length === 0 ? items : [new RefItemSeparator(this.type), ...items]; + } + + constructor(private readonly repository: Repository) { + super(RefType.Head); + } +} + +class CheckoutItemsProcessor extends RefItemsProcessor { + + private defaultButtons: RemoteSourceActionButton[] | undefined; + + constructor( + processors: RefProcessor[], + private readonly repository: Repository, + private readonly buttons: Map, + private readonly detached = false) { + super(processors); + + // Default button(s) + const remote = repository.remotes.find(r => r.pushUrl === repository.HEAD?.remote || r.fetchUrl === repository.HEAD?.remote) ?? repository.remotes[0]; + const remoteUrl = remote?.pushUrl ?? remote?.fetchUrl; + if (remoteUrl) { + this.defaultButtons = buttons.get(remoteUrl); + } + } + + override processRefs(refs: Ref[]): QuickPickItem[] { + for (const ref of refs) { + if (!this.detached && ref.name === 'origin/HEAD') { + continue; + } + + for (const processor of this.processors) { + if (processor.processRef(ref)) { + break; + } + } + } + + const result: QuickPickItem[] = []; + for (const processor of this.processors) { + for (const item of processor.items) { + if (!(item instanceof RefItem)) { + result.push(item); + continue; + } + + // Button(s) + if (item.refRemote) { + const matchingRemote = this.repository.remotes.find((remote) => remote.name === item.refRemote); + const remoteUrl = matchingRemote?.pushUrl ?? matchingRemote?.fetchUrl; + if (remoteUrl) { + item.buttons = this.buttons.get(item.refRemote); + } + } else { + item.buttons = this.defaultButtons; + } + + result.push(item); + } + } + + return result; + } +} + +function getCheckoutRefProcessor(repository: Repository, type: string): RefProcessor | undefined { + switch (type) { + case 'local': + return new CheckoutRefProcessor(repository); + case 'remote': + return new RefProcessor(RefType.RemoteHead, CheckoutRemoteHeadItem); + case 'tags': + return new RefProcessor(RefType.Tag, CheckoutTagItem); + default: + return undefined; + } } function getRepositoryLabel(repositoryRoot: string): string { @@ -391,12 +564,6 @@ function sanitizeRemoteName(name: string) { return name && name.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, '-'); } -class TagItem implements QuickPickItem { - get label(): string { return `$(tag) ${this.ref.name ?? ''}`; } - get description(): string { return this.ref.commit?.substr(0, 8) ?? ''; } - constructor(readonly ref: Ref) { } -} - enum PushType { Push, PushTo, @@ -2230,7 +2397,7 @@ export class CommandCenter { const picks: QuickPickItem[] = []; if (!opts?.detached) { - picks.push(createBranch, createBranchFrom, checkoutDetached, { label: '', kind: QuickPickItemKind.Separator }); + picks.push(createBranch, createBranchFrom, checkoutDetached); } const quickpick = window.createQuickPick(); @@ -2272,7 +2439,7 @@ export class CommandCenter { const item = choice as CheckoutItem; try { - await item.run(opts); + await item.run(repository, opts); } catch (err) { if (err.gitErrorCode !== GitErrorCodes.DirtyWorkTree) { throw err; @@ -2285,10 +2452,10 @@ export class CommandCenter { if (choice === force) { await this.cleanAll(repository); - await item.run(opts); + await item.run(repository, opts); } else if (choice === stash || choice === migrate) { if (await this._stash(repository)) { - await item.run(opts); + await item.run(repository, opts); if (choice === migrate) { await this.stashPopLatest(repository); @@ -2408,7 +2575,14 @@ export class CommandCenter { if (from) { const getRefPicks = async () => { - return [new HEADItem(repository), ...await createCheckoutItems(repository)]; + const refs = await repository.getRefs(); + const refProcessors = new RefItemsProcessor([ + new RefProcessor(RefType.Head), + new RefProcessor(RefType.RemoteHead), + new RefProcessor(RefType.Tag) + ]); + + return [new HEADItem(repository), ...refProcessors.processRefs(refs)]; }; const placeHolder = l10n.t('Select a ref to create the branch from'); @@ -2418,7 +2592,7 @@ export class CommandCenter { return; } - if (choice.refName) { + if (choice instanceof RefItem && choice.refName) { target = choice.refName; } } @@ -2448,10 +2622,10 @@ export class CommandCenter { const placeHolder = l10n.t('Select a branch to delete'); const choice = await window.showQuickPick(getBranchPicks(), { placeHolder }); - if (!choice || !choice.branchName) { + if (!choice || !choice.refName) { return; } - name = choice.branchName; + name = choice.refName; run = force => choice.run(repository, force); } @@ -2499,32 +2673,23 @@ export class CommandCenter { @command('git.merge', { repository: true }) async merge(repository: Repository): Promise { - const config = workspace.getConfiguration('git'); - const checkoutType = config.get('checkoutType'); - const includeRemotes = checkoutType === 'all' || checkoutType === 'remote' || checkoutType?.includes('remote'); - - const getBranchPicks = async (): Promise => { + const getQuickPickItems = async (): Promise => { const refs = await repository.getRefs(); + const itemsProcessor = new RefItemsProcessor([ + new RefProcessor(RefType.Head, MergeItem), + new RefProcessor(RefType.RemoteHead, MergeItem), + new RefProcessor(RefType.Tag, MergeItem) + ]); - const heads = refs.filter(ref => ref.type === RefType.Head) - .filter(ref => ref.name || ref.commit) - .map(ref => new MergeItem(repository, ref as Branch)); - - const remoteHeads = (includeRemotes ? refs.filter(ref => ref.type === RefType.RemoteHead) : []) - .filter(ref => ref.name || ref.commit) - .map(ref => new MergeItem(repository, ref as Branch)); - - return [...heads, ...remoteHeads]; + return itemsProcessor.processRefs(refs); }; - const placeHolder = l10n.t('Select a branch to merge from'); - const choice = await window.showQuickPick(getBranchPicks(), { placeHolder }); + const placeHolder = l10n.t('Select a branch or tag to merge from'); + const choice = await window.showQuickPick(getQuickPickItems(), { placeHolder }); - if (!choice) { - return; + if (choice instanceof MergeItem) { + await choice.run(repository); } - - await choice.run(); } @command('git.mergeAbort', { repository: true }) @@ -2534,45 +2699,19 @@ export class CommandCenter { @command('git.rebase', { repository: true }) async rebase(repository: Repository): Promise { - const config = workspace.getConfiguration('git'); - const checkoutType = config.get('checkoutType'); - const includeRemotes = checkoutType === 'all' || checkoutType === 'remote' || checkoutType?.includes('remote'); - - const getBranchPicks = async () => { + const getQuickPickItems = async (): Promise => { const refs = await repository.getRefs(); + const itemsProcessor = new RebaseItemsProcessors(repository); - const heads = refs.filter(ref => ref.type === RefType.Head) - .filter(ref => ref.name !== repository.HEAD?.name) - .filter(ref => ref.name || ref.commit); - - const remoteHeads = (includeRemotes ? refs.filter(ref => ref.type === RefType.RemoteHead) : []) - .filter(ref => ref.name || ref.commit); - - const picks = [...heads, ...remoteHeads].map(ref => new RebaseItem(ref)); - - // set upstream branch as first - if (repository.HEAD?.upstream) { - const upstreamName = `${repository.HEAD?.upstream.remote}/${repository.HEAD?.upstream.name}`; - const index = picks.findIndex(e => e.ref.name === upstreamName); - - if (index > -1) { - const [ref] = picks.splice(index, 1); - ref.description = '(upstream)'; - picks.unshift(ref); - } - } - - return picks; + return itemsProcessor.processRefs(refs); }; const placeHolder = l10n.t('Select a branch to rebase onto'); - const choice = await window.showQuickPick(getBranchPicks(), { placeHolder }); + const choice = await window.showQuickPick(getQuickPickItems(), { placeHolder }); - if (!choice) { - return; + if (choice instanceof RebaseItem) { + await choice.run(repository); } - - await choice.run(repository); } @command('git.createTag', { repository: true }) @@ -2599,16 +2738,16 @@ export class CommandCenter { @command('git.deleteTag', { repository: true }) async deleteTag(repository: Repository): Promise { - const tagPicks = async (): Promise => { + const tagPicks = async (): Promise => { const remoteTags = await repository.getRefs({ pattern: 'refs/tags' }); - return remoteTags.length === 0 ? [{ label: l10n.t('$(info) This repository has no tags.') }] : remoteTags.map(ref => new TagItem(ref)); + return remoteTags.length === 0 ? [{ label: l10n.t('$(info) This repository has no tags.') }] : remoteTags.map(ref => new TagDeleteItem(ref)); }; const placeHolder = l10n.t('Select a tag to delete'); - const choice = await window.showQuickPick(tagPicks(), { placeHolder }); + const choice = await window.showQuickPick(tagPicks(), { placeHolder }); - if (choice && choice instanceof TagItem && choice.ref.name) { - await repository.deleteTag(choice.ref.name); + if (choice instanceof TagDeleteItem) { + await choice.run(repository); } } @@ -2635,7 +2774,7 @@ export class CommandCenter { remoteName = remotePick.remoteName; } - const remoteTagPicks = async (): Promise => { + const remoteTagPicks = async (): Promise => { const remoteTagsRaw = await repository.getRemoteRefs(remoteName, { tags: true }); // Deduplicate annotated and lightweight tags @@ -2650,14 +2789,14 @@ export class CommandCenter { } } - return remoteTags.length === 0 ? [{ label: l10n.t('$(info) Remote "{0}" has no tags.', remoteName) }] : remoteTags.map(ref => new TagItem(ref)); + return remoteTags.length === 0 ? [{ label: l10n.t('$(info) Remote "{0}" has no tags.', remoteName) }] : remoteTags.map(ref => new RemoteTagDeleteItem(ref)); }; - const tagPickPlaceholder = l10n.t('Select a tag to delete'); - const remoteTagPick = await window.showQuickPick(remoteTagPicks(), { placeHolder: tagPickPlaceholder }); + const tagPickPlaceholder = l10n.t('Select a remote tag to delete'); + const remoteTagPick = await window.showQuickPick(remoteTagPicks(), { placeHolder: tagPickPlaceholder }); - if (remoteTagPick && remoteTagPick instanceof TagItem && remoteTagPick.ref.name) { - await repository.deleteRemoteTag(remoteName, remoteTagPick.ref.name); + if (remoteTagPick instanceof RemoteTagDeleteItem) { + await remoteTagPick.run(repository, remoteName); } } @@ -2747,21 +2886,20 @@ export class CommandCenter { remoteName = remotePick.label; } - const getBranchPicks = async (): Promise => { - const remoteRefs = await repository.getRefs(); - const remoteRefsFiltered = remoteRefs.filter(r => (r.remote === remoteName)); - return remoteRefsFiltered.map(r => ({ label: r.name! })); + const getBranchPicks = async (): Promise => { + const remoteRefs = await repository.getRefs({ pattern: `refs/remotes/${remoteName}/` }); + return remoteRefs.map(r => new RefItem(r)); }; const branchPlaceHolder = l10n.t('Pick a branch to pull from'); const branchPick = await window.showQuickPick(getBranchPicks(), { placeHolder: branchPlaceHolder }); - if (!branchPick) { + if (!branchPick || !branchPick.refName) { return; } const remoteCharCnt = remoteName.length; - await repository.pullFrom(false, remoteName, branchPick.label.slice(remoteCharCnt + 1)); + await repository.pullFrom(false, remoteName, branchPick.refName.slice(remoteCharCnt + 1)); } @command('git.pull', { repository: true }) From 01b8428485c31b6f5a899a54a3196bf64335cd81 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 29 Dec 2023 08:39:35 +0100 Subject: [PATCH 12/26] VSCode always restores the panel size if toggling primary side bar. (fix #195119) (#201567) --- src/vs/workbench/browser/layout.ts | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 0efb79865fe..a4c1c34b1a4 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -1274,6 +1274,15 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.focusPart(Parts.EDITOR_PART, getWindow(this.activeContainer)); } + private focusPanelOrEditor(): void { + const activePanel = this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel); + if ((this.hasFocus(Parts.PANEL_PART) || !this.isVisible(Parts.EDITOR_PART)) && activePanel) { + activePanel.focus(); // prefer panel if it has focus or editor is hidden + } else { + this.focus(); // otherwise focus editor + } + } + getMaximumEditorDimensions(container: HTMLElement): IDimension { const targetWindow = getWindow(container); const containerDimension = this.getContainerDimension(container); @@ -1746,14 +1755,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // If sidebar becomes hidden, also hide the current active Viewlet if any if (hidden && this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar)) { this.paneCompositeService.hideActivePaneComposite(ViewContainerLocation.Sidebar); - - // Pass Focus to Editor or Panel if Sidebar is now hidden - const activePanel = this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel); - if (this.hasFocus(Parts.PANEL_PART) && activePanel) { - activePanel.focus(); - } else { - this.focus(); - } + this.focusPanelOrEditor(); } // If sidebar becomes visible, show last active Viewlet or default viewlet @@ -1986,14 +1988,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // If auxiliary bar becomes hidden, also hide the current active pane composite if any if (hidden && this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.AuxiliaryBar)) { this.paneCompositeService.hideActivePaneComposite(ViewContainerLocation.AuxiliaryBar); - - // Pass Focus to Editor or Panel if Auxiliary Bar is now hidden - const activePanel = this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel); - if (this.hasFocus(Parts.PANEL_PART) && activePanel) { - activePanel.focus(); - } else { - this.focus(); - } + this.focusPanelOrEditor(); } // If auxiliary bar becomes visible, show last active pane composite or default pane composite From 14d01569e5907e43d0966806af8c4c593a109f5c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 29 Dec 2023 08:46:38 +0100 Subject: [PATCH 13/26] Aux window: support to restore aux windows and editors (fix #195887) (#201291) --- .../electron-main/auxiliaryWindow.ts | 7 +- .../electron-main/lifecycleMainService.ts | 34 +++++ .../electron-main/workbenchTestServices.ts | 2 + .../api/browser/mainThreadEditorTabs.ts | 2 +- .../browser/actions/developerActions.ts | 4 +- src/vs/workbench/browser/contextkeys.ts | 2 +- src/vs/workbench/browser/layout.ts | 10 +- src/vs/workbench/browser/part.ts | 4 +- .../parts/editor/auxiliaryEditorPart.ts | 27 +++- .../browser/parts/editor/editorGroupView.ts | 8 +- .../browser/parts/editor/editorPart.ts | 31 ++-- .../browser/parts/editor/editorParts.ts | 136 ++++++++++++++++-- .../browser/parts/editor/editorsObserver.ts | 2 +- .../browser/parts/statusbar/statusbarPart.ts | 6 +- .../browser/parts/titlebar/titlebarPart.ts | 6 +- .../services/notebookEditorServiceImpl.ts | 2 +- .../browser/webviewPanel.contribution.ts | 2 +- .../browser/auxiliaryWindowService.ts | 33 +++-- .../services/editor/browser/editorService.ts | 2 +- .../editor/common/editorGroupsService.ts | 72 +++++----- .../history/browser/historyService.ts | 4 +- 21 files changed, 304 insertions(+), 92 deletions(-) diff --git a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts index 8317fe7bcd2..cf36f0950b9 100644 --- a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts +++ b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts @@ -6,6 +6,7 @@ import { BrowserWindow, WebContents } from 'electron'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; +import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { ILogService } from 'vs/platform/log/common/log'; import { IStateService } from 'vs/platform/state/node/state'; import { IBaseWindow } from 'vs/platform/window/electron-main/window'; @@ -33,7 +34,8 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { @IEnvironmentMainService environmentMainService: IEnvironmentMainService, @ILogService private readonly logService: ILogService, @IConfigurationService configurationService: IConfigurationService, - @IStateService stateService: IStateService + @IStateService stateService: IStateService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService ) { super(configurationService, stateService, environmentMainService); @@ -61,6 +63,9 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { // Disable Menu window.setMenu(null); + + // Lifecycle + this.lifecycleMainService.registerAuxWindow(this); } } } diff --git a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts index 82640175b1c..ed098115159 100644 --- a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts +++ b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts @@ -18,6 +18,7 @@ import { IStateService } from 'vs/platform/state/node/state'; import { ICodeWindow, LoadReason, UnloadReason } from 'vs/platform/window/electron-main/window'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; +import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow'; export const ILifecycleMainService = createDecorator('lifecycleMainService'); @@ -131,6 +132,11 @@ export interface ILifecycleMainService { */ registerWindow(window: ICodeWindow): void; + /** + * Make a `IAuxiliaryWindow` known to the lifecycle main service. + */ + registerAuxWindow(auxWindow: IAuxiliaryWindow): void; + /** * Reload a window. All lifecycle event handlers are triggered. */ @@ -472,6 +478,34 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe }); } + registerAuxWindow(auxWindow: IAuxiliaryWindow): void { + const win = assertIsDefined(auxWindow.win); + + win.on('close', e => { + this.trace(`Lifecycle#auxWindow.on('close') - window ID ${auxWindow.id}`); + + if (this._quitRequested) { + this.trace(`Lifecycle#auxWindow.on('close') - preventDefault() because quit requested`); + + // When quit is requested, Electron will close all + // auxiliary windows before closing the main windows. + // This prevents us from storing the auxiliary window + // state on shutdown and thus we prevent closing if + // quit is requested. + // + // Interestingly, this will not prevent the application + // from quitting because the auxiliary windows will still + // close once the owning window closes. + + e.preventDefault(); + } + }); + + win.on('closed', () => { + this.trace(`Lifecycle#auxWindow.on('closed') - window ID ${auxWindow.id}`); + }); + } + async reload(window: ICodeWindow, cli?: NativeParsedArgs): Promise { // Only reload when the window has not vetoed this diff --git a/src/vs/platform/test/electron-main/workbenchTestServices.ts b/src/vs/platform/test/electron-main/workbenchTestServices.ts index 639f1fab2bc..65beabd3b2a 100644 --- a/src/vs/platform/test/electron-main/workbenchTestServices.ts +++ b/src/vs/platform/test/electron-main/workbenchTestServices.ts @@ -5,6 +5,7 @@ import { Promises } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; +import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { ILifecycleMainService, IRelaunchHandler, LifecycleMainPhase, ShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IStateService } from 'vs/platform/state/node/state'; @@ -41,6 +42,7 @@ export class TestLifecycleMainService implements ILifecycleMainService { phase = LifecycleMainPhase.Ready; registerWindow(window: ICodeWindow): void { } + registerAuxWindow(auxWindow: IAuxiliaryWindow): void { } async reload(window: ICodeWindow, cli?: NativeParsedArgs): Promise { } async unload(window: ICodeWindow, reason: UnloadReason): Promise { return true; } setRelaunchHandler(handler: IRelaunchHandler): void { } diff --git a/src/vs/workbench/api/browser/mainThreadEditorTabs.ts b/src/vs/workbench/api/browser/mainThreadEditorTabs.ts index 5f2f3610314..d23f8e91fd5 100644 --- a/src/vs/workbench/api/browser/mainThreadEditorTabs.ts +++ b/src/vs/workbench/api/browser/mainThreadEditorTabs.ts @@ -70,7 +70,7 @@ export class MainThreadEditorTabs implements MainThreadEditorTabsShape { this._dispoables.add(this._editorGroupsService.onDidRemoveGroup(() => this._createTabsModel())); // Once everything is read go ahead and initialize the model - this._editorGroupsService.mainPart.whenReady.then(() => this._createTabsModel()); + this._editorGroupsService.whenReady.then(() => this._createTabsModel()); } dispose(): void { diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index 14430b7c668..3d3208a6162 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -39,6 +39,7 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import product from 'vs/platform/product/common/product'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; class InspectContextKeysAction extends Action2 { @@ -467,6 +468,7 @@ class RemoveLargeStorageEntriesAction extends Action2 { const quickInputService = accessor.get(IQuickInputService); const userDataProfileService = accessor.get(IUserDataProfileService); const dialogService = accessor.get(IDialogService); + const environmentService = accessor.get(IEnvironmentService); interface IStorageItem extends IQuickPickItem { readonly key: string; @@ -485,7 +487,7 @@ class RemoveLargeStorageEntriesAction extends Action2 { for (const target of [StorageTarget.MACHINE, StorageTarget.USER]) { for (const key of storageService.keys(scope, target)) { const value = storageService.get(key, scope); - if (value && value.length > RemoveLargeStorageEntriesAction.SIZE_THRESHOLD) { + if (value && (!environmentService.isBuilt /* show all keys in dev */ || value.length > RemoveLargeStorageEntriesAction.SIZE_THRESHOLD)) { items.push({ key, scope, diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index 5c5f96531a9..4c142fb8246 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -227,7 +227,7 @@ export class WorkbenchContextKeysHandler extends Disposable { } private registerListeners(): void { - this.editorGroupService.mainPart.whenReady.then(() => { + this.editorGroupService.whenReady.then(() => { this.updateEditorAreaContextKeys(); this.updateEditorContextKeys(); }); diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index a4c1c34b1a4..4d64eeb391c 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -333,7 +333,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Wait to register these listeners after the editor group service // is ready to avoid conflicts on startup - this.editorGroupService.mainPart.whenRestored.then(() => { + this.editorGroupService.whenRestored.then(() => { // Restore main editor part on any editor change in main part this._register(this.mainPartEditorService.onDidVisibleEditorsChange(showEditorIfHidden)); @@ -494,7 +494,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.updateMenubarVisibility(!!skipLayout); // Centered Layout - this.editorGroupService.mainPart.whenRestored.then(() => { + this.editorGroupService.whenRestored.then(() => { this.centerMainEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED), skipLayout); }); } @@ -781,7 +781,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Empty workbench configured to open untitled file if empty else if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && this.configurationService.getValue('workbench.startupEditor') === 'newUntitledFile') { - if (this.editorGroupService.mainPart.hasRestorableState) { + if (this.editorGroupService.hasRestorableState) { return []; // do not open any empty untitled file if we restored groups/editors from previous session } @@ -854,7 +854,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi mark('code/willRestoreEditors'); // first ensure the editor part is ready - await this.editorGroupService.mainPart.whenReady; + await this.editorGroupService.whenReady; mark('code/restoreEditors/editorGroupsReady'); // apply editor layout if any @@ -910,7 +910,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi layoutRestoredPromises.push( Promise.all([ openEditorsPromise?.finally(() => mark('code/restoreEditors/editorsOpened')), - this.editorGroupService.mainPart.whenRestored.finally(() => mark('code/restoreEditors/editorGroupsRestored')) + this.editorGroupService.whenRestored.finally(() => mark('code/restoreEditors/editorGroupsRestored')) ]).finally(() => { // the `code/didRestoreEditors` perf mark is specifically // for when visible editors have resolved, so we only mark diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index b9e07cb213a..7ae271c65cc 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -12,7 +12,7 @@ import { ISerializableView, IViewSize } from 'vs/base/browser/ui/grid/grid'; import { Event, Emitter } from 'vs/base/common/event'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { assertIsDefined } from 'vs/base/common/types'; -import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; export interface IPartOptions { readonly hasTitle?: boolean; @@ -187,7 +187,7 @@ export interface IMultiWindowPart { readonly element: HTMLElement; } -export abstract class MultiWindowParts extends Disposable { +export abstract class MultiWindowParts extends Component { protected readonly _parts = new Set(); get parts() { return Array.from(this._parts); } diff --git a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts index 8d1ea43513e..e6322146585 100644 --- a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts +++ b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts @@ -15,7 +15,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { getTitleBarStyle } from 'vs/platform/window/common/window'; import { IEditorGroupView, IEditorPartsView } from 'vs/workbench/browser/parts/editor/editor'; -import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; +import { EditorPart, IEditorPartUIState } from 'vs/workbench/browser/parts/editor/editorPart'; import { IAuxiliaryTitlebarPart } from 'vs/workbench/browser/parts/titlebar/titlebarPart'; import { WindowTitle } from 'vs/workbench/browser/parts/titlebar/windowTitle'; import { IAuxiliaryWindowOpenOptions, IAuxiliaryWindowService } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService'; @@ -27,6 +27,16 @@ import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecy import { IStatusbarService } from 'vs/workbench/services/statusbar/browser/statusbar'; import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; +export interface IAuxiliaryEditorPartOpenOptions extends IAuxiliaryWindowOpenOptions { + readonly state?: IEditorPartUIState; +} + +export interface ICreateAuxiliaryEditorPartResult { + readonly part: AuxiliaryEditorPartImpl; + readonly instantiationService: IInstantiationService; + readonly disposables: DisposableStore; +} + export class AuxiliaryEditorPart { private static STATUS_BAR_VISIBILITY = 'workbench.statusBar.visible'; @@ -44,7 +54,7 @@ export class AuxiliaryEditorPart { ) { } - async create(label: string, options?: IAuxiliaryWindowOpenOptions): Promise<{ readonly part: AuxiliaryEditorPartImpl; readonly instantiationService: IInstantiationService; readonly disposables: DisposableStore }> { + async create(label: string, options?: IAuxiliaryEditorPartOpenOptions): Promise { function computeEditorPartHeightOffset(): number { let editorPartHeightOffset = 0; @@ -90,9 +100,9 @@ export class AuxiliaryEditorPart { editorPartContainer.style.position = 'relative'; auxiliaryWindow.container.appendChild(editorPartContainer); - const editorPart = disposables.add(this.instantiationService.createInstance(AuxiliaryEditorPartImpl, auxiliaryWindow.window.vscodeWindowId, this.editorPartsView, label)); + const editorPart = disposables.add(this.instantiationService.createInstance(AuxiliaryEditorPartImpl, auxiliaryWindow.window.vscodeWindowId, this.editorPartsView, options?.state, label)); disposables.add(this.editorPartsView.registerPart(editorPart)); - editorPart.create(editorPartContainer, { restorePreviousState: false }); + editorPart.create(editorPartContainer); // Titlebar let titlebarPart: IAuxiliaryTitlebarPart | undefined = undefined; @@ -140,7 +150,7 @@ export class AuxiliaryEditorPart { // Lifecycle const editorCloseListener = disposables.add(Event.once(editorPart.onWillClose)(() => auxiliaryWindow.window.close())); - disposables.add(Event.once(auxiliaryWindow.onWillClose)(() => { + disposables.add(Event.once(auxiliaryWindow.onUnload)(() => { if (disposables.isDisposed) { return; // the close happened as part of an earlier dispose call } @@ -187,6 +197,7 @@ class AuxiliaryEditorPartImpl extends EditorPart implements IAuxiliaryEditorPart constructor( readonly windowId: number, editorPartsView: IEditorPartsView, + private readonly state: IEditorPartUIState | undefined, groupsLabel: string, @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @@ -231,8 +242,12 @@ class AuxiliaryEditorPartImpl extends EditorPart implements IAuxiliaryEditorPart this.doClose(false /* do not merge any groups to main part */); } + protected override loadState(): IEditorPartUIState | undefined { + return this.state; + } + protected override saveState(): void { - return; // TODO support auxiliary editor state + return; // disabled, auxiliary editor part state is tracked outside } close(): void { diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 64a236f7be4..bb23015b994 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -507,13 +507,17 @@ export class EditorGroupView extends Themable implements IEditorGroupView { options.pinned = this.model.isPinned(activeEditor); // preserve pinned state options.sticky = this.model.isSticky(activeEditor); // preserve sticky state - options.preserveFocus = true; // handle focus after editor is opened + options.preserveFocus = true; // handle focus after editor is restored + + const internalOptions: IInternalEditorOpenOptions = { + preserveWindowOrder: true // handle window order after editor is restored + }; const activeElement = getActiveElement(); // Show active editor (intentionally not using async to keep // `restoreEditors` from executing in same stack) - return this.doShowEditor(activeEditor, { active: true, isNew: false /* restored */ }, options).then(() => { + return this.doShowEditor(activeEditor, { active: true, isNew: false /* restored */ }, options, internalOptions).then(() => { // Set focused now if this is the active group and focus has // not changed meanwhile. This prevents focus from being diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 1590335d17d..8aac8aa2023 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -36,7 +36,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { EditorPartMaximizedEditorGroupContext, EditorPartMultipleEditorGroupsContext, IsAuxiliaryEditorPartContext } from 'vs/workbench/common/contextkeys'; -interface IEditorPartUIState { +export interface IEditorPartUIState { readonly serializedGrid: ISerializedGrid; readonly activeGroup: GroupIdentifier; readonly mostRecentActiveGroups: GroupIdentifier[]; @@ -251,6 +251,9 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { return !!this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY]; } + private _didRestoreState = false; + get didRestoreState(): boolean { return this._didRestoreState; } + getGroups(order = GroupsOrder.CREATION_TIME): IEditorGroupView[] { switch (order) { case GroupsOrder.CREATION_TIME: @@ -1167,7 +1170,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { } private doCreateGridControlWithPreviousState(): boolean { - const uiState: IEditorPartUIState = this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY]; + const uiState: IEditorPartUIState | undefined = this.loadState(); if (uiState?.serializedGrid) { try { @@ -1177,8 +1180,8 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { // Grid Widget this.doCreateGridControlWithState(uiState.serializedGrid, uiState.activeGroup); - // Ensure last active group has focus - this._activeGroup.focus(); + // Remember that we did restore previous state + this._didRestoreState = true; } catch (error) { // Log error @@ -1312,16 +1315,10 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { // Persist grid UI state if (this.gridWidget) { - const uiState: IEditorPartUIState = { - serializedGrid: this.gridWidget.serialize(), - activeGroup: this._activeGroup.id, - mostRecentActiveGroups: this.mostRecentActiveGroups - }; - if (this.isEmpty) { delete this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY]; } else { - this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] = uiState; + this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] = this.createState(); } } @@ -1338,6 +1335,18 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { super.saveState(); } + protected loadState(): IEditorPartUIState | undefined { + return this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY]; + } + + createState(): IEditorPartUIState { + return { + serializedGrid: this.gridWidget.serialize(), + activeGroup: this._activeGroup.id, + mostRecentActiveGroups: this.mostRecentActiveGroups + }; + } + toJSON(): object { return { type: Parts.EDITOR_PART diff --git a/src/vs/workbench/browser/parts/editor/editorParts.ts b/src/vs/workbench/browser/parts/editor/editorParts.ts index 155d6d153d2..7f36225bf2c 100644 --- a/src/vs/workbench/browser/parts/editor/editorParts.ts +++ b/src/vs/workbench/browser/parts/editor/editorParts.ts @@ -8,14 +8,28 @@ import { EditorGroupLayout, GroupDirection, GroupLocation, GroupOrientation, Gro import { Emitter } from 'vs/base/common/event'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { GroupIdentifier } from 'vs/workbench/common/editor'; -import { EditorPart, MainEditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; +import { EditorPart, IEditorPartUIState, MainEditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { IEditorGroupView, IEditorPartsView } from 'vs/workbench/browser/parts/editor/editor'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IAuxiliaryWindowOpenOptions } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService'; -import { distinct } from 'vs/base/common/arrays'; -import { AuxiliaryEditorPart } from 'vs/workbench/browser/parts/editor/auxiliaryEditorPart'; +import { distinct, firstOrDefault } from 'vs/base/common/arrays'; +import { AuxiliaryEditorPart, IAuxiliaryEditorPartOpenOptions } from 'vs/workbench/browser/parts/editor/auxiliaryEditorPart'; import { MultiWindowParts } from 'vs/workbench/browser/part'; +import { DeferredPromise } from 'vs/base/common/async'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IRectangle } from 'vs/platform/window/common/window'; +import { getWindow } from 'vs/base/browser/dom'; + +interface IEditorPartsUIState { + readonly auxiliary: IAuxiliaryEditorPartState[]; + readonly mru: number[]; +} + +interface IAuxiliaryEditorPartState { + readonly state: IEditorPartUIState; + readonly bounds?: IRectangle; +} export class EditorParts extends MultiWindowParts implements IEditorGroupsService, IEditorPartsView { @@ -23,14 +37,18 @@ export class EditorParts extends MultiWindowParts implements IEditor readonly mainPart = this._register(this.createMainEditorPart()); - private readonly mostRecentActiveParts = [this.mainPart]; + private mostRecentActiveParts = [this.mainPart]; constructor( - @IInstantiationService private readonly instantiationService: IInstantiationService + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IStorageService storageService: IStorageService, + @IThemeService themeService: IThemeService ) { - super(); + super('workbench.editorParts', themeService, storageService); this._register(this.registerPart(this.mainPart)); + + this.restoreParts(); } protected createMainEditorPart(): MainEditorPart { @@ -42,7 +60,7 @@ export class EditorParts extends MultiWindowParts implements IEditor private readonly _onDidCreateAuxiliaryEditorPart = this._register(new Emitter()); readonly onDidCreateAuxiliaryEditorPart = this._onDidCreateAuxiliaryEditorPart.event; - async createAuxiliaryEditorPart(options?: IAuxiliaryWindowOpenOptions): Promise { + async createAuxiliaryEditorPart(options?: IAuxiliaryEditorPartOpenOptions): Promise { const { part, instantiationService, disposables } = await this.instantiationService.createInstance(AuxiliaryEditorPart, this).create(this.getGroupsLabel(this._parts.size), options); // Events @@ -156,6 +174,108 @@ export class EditorParts extends MultiWindowParts implements IEditor //#endregion + //#region Lifecycle / State + + private static readonly EDITOR_PARTS_UI_STATE_STORAGE_KEY = 'editorparts.state'; + + private readonly workspaceMemento = this.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); + + private _isReady = false; + get isReady(): boolean { return this._isReady; } + + private readonly whenReadyPromise = new DeferredPromise(); + readonly whenReady = this.whenReadyPromise.p; + + private readonly whenRestoredPromise = new DeferredPromise(); + readonly whenRestored = this.whenRestoredPromise.p; + + private async restoreParts(): Promise { + + // Join on the main part being ready to pick + // the right moment to begin restoring. + // The main part is automatically being created + // as part of the overall startup process. + await this.mainPart.whenReady; + + // Only attempt to restore auxiliary editor parts + // when the main part did restore. It is possible + // that restoring was not attempted because specific + // editors were opened. + if (this.mainPart.didRestoreState) { + const uiState: IEditorPartsUIState | undefined = this.workspaceMemento[EditorParts.EDITOR_PARTS_UI_STATE_STORAGE_KEY]; + if (uiState?.auxiliary.length) { + const auxiliaryEditorPartPromises: Promise[] = []; + + // Create auxiliary editor parts + for (const auxiliaryEditorPartState of uiState.auxiliary) { + auxiliaryEditorPartPromises.push(this.createAuxiliaryEditorPart({ + bounds: auxiliaryEditorPartState.bounds, + state: auxiliaryEditorPartState.state + })); + } + + // Await creation + await Promise.allSettled(auxiliaryEditorPartPromises); + + // Update MRU list + if (uiState.mru.length === this.parts.length) { + this.mostRecentActiveParts = uiState.mru.map(index => this.parts[index]); + } else { + this.mostRecentActiveParts = [...this.parts]; + } + } + } + + // Await ready + await Promise.allSettled(this.parts.map(part => part.whenReady)); + + const mostRecentActivePart = firstOrDefault(this.mostRecentActiveParts); + mostRecentActivePart?.activeGroup.focus(); + + this._isReady = true; + this.whenReadyPromise.complete(); + + // Await restored + await Promise.allSettled(this.parts.map(part => part.whenRestored)); + this.whenRestoredPromise.complete(); + } + + protected override saveState(): void { + const uiState: IEditorPartsUIState = { + auxiliary: this.parts.filter(part => part !== this.mainPart).map(part => { + return { + state: part.createState(), + bounds: (() => { + const auxiliaryWindow = getWindow(part.getContainer()); + if (auxiliaryWindow) { + return { + x: auxiliaryWindow.screenX, + y: auxiliaryWindow.screenY, + width: auxiliaryWindow.outerWidth, + height: auxiliaryWindow.outerHeight + }; + } + + return undefined; + })() + }; + }), + mru: this.mostRecentActiveParts.map(part => this.parts.indexOf(part)) + }; + + if (uiState.auxiliary.length === 0) { + delete this.workspaceMemento[EditorParts.EDITOR_PARTS_UI_STATE_STORAGE_KEY]; + } else { + this.workspaceMemento[EditorParts.EDITOR_PARTS_UI_STATE_STORAGE_KEY] = uiState; + } + } + + get hasRestorableState(): boolean { + return this.parts.some(part => part.hasRestorableState); + } + + //#endregion + //#region Events private readonly _onDidActiveGroupChange = this._register(new Emitter()); diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts index e8168535369..c6a09b4fcfd 100644 --- a/src/vs/workbench/browser/parts/editor/editorsObserver.ts +++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts @@ -460,7 +460,7 @@ export class EditorsObserver extends Disposable { private async loadState(): Promise { if (this.editorGroupsContainer === this.editorGroupService.mainPart || this.editorGroupsContainer === this.editorGroupService) { - await this.editorGroupService.mainPart.whenReady; + await this.editorGroupService.whenReady; } // Previous state: Load editors map from persisted state diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 98f233b324c..568c01bf5a1 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -708,9 +708,11 @@ export class StatusbarService extends MultiWindowParts implements readonly mainPart = this._register(this.instantiationService.createInstance(MainStatusbarPart)); constructor( - @IInstantiationService private readonly instantiationService: IInstantiationService + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IStorageService storageService: IStorageService, + @IThemeService themeService: IThemeService ) { - super(); + super('workbench.statusBarService', themeService, storageService); this._register(this.registerPart(this.mainPart)); } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index df69f28b7c5..1f98cb50ef7 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -81,9 +81,11 @@ export class BrowserTitleService extends MultiWindowParts i readonly mainPart = this._register(this.createMainTitlebarPart()); constructor( - @IInstantiationService protected readonly instantiationService: IInstantiationService + @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IStorageService storageService: IStorageService, + @IThemeService themeService: IThemeService ) { - super(); + super('workbench.titleService', themeService, storageService); this._register(this.registerPart(this.mainPart)); } diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts index b45c469242f..e5baf7c6af3 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts @@ -77,7 +77,7 @@ export class NotebookEditorWidgetService implements INotebookEditorService { groupListener.set(id, listeners); }; this._disposables.add(editorGroupService.onDidAddGroup(onNewGroup)); - editorGroupService.mainPart.whenReady.then(() => editorGroupService.groups.forEach(onNewGroup)); + editorGroupService.whenReady.then(() => editorGroupService.groups.forEach(onNewGroup)); // group removed -> clean up listeners, clean up widgets this._disposables.add(editorGroupService.onDidRemoveGroup(group => { diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts index cc01ea4ed52..8d49c0221a1 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts @@ -36,7 +36,7 @@ class WebviewPanelContribution extends Disposable implements IWorkbenchContribut super(); // Add all the initial groups to be listened to - this.editorGroupService.mainPart.whenReady.then(() => this.editorGroupService.groups.forEach(group => { + this.editorGroupService.whenReady.then(() => this.editorGroupService.groups.forEach(group => { this.registerGroupListener(group); })); diff --git a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts index 3bb124ffa6f..bcb5c79f51b 100644 --- a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts @@ -45,7 +45,10 @@ export interface IAuxiliaryWindowService { export interface IAuxiliaryWindow extends IDisposable { readonly onDidLayout: Event; - readonly onWillClose: Event; + + readonly onBeforeUnload: Event; + readonly onUnload: Event; + readonly whenStylesHaveLoaded: Promise; readonly window: CodeWindow; @@ -59,8 +62,11 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { private readonly _onDidLayout = this._register(new Emitter()); readonly onDidLayout = this._onDidLayout.event; - private readonly _onWillClose = this._register(new Emitter()); - readonly onWillClose = this._onWillClose.event; + private readonly _onBeforeUnload = this._register(new Emitter()); + readonly onBeforeUnload = this._onBeforeUnload.event; + + private readonly _onUnload = this._register(new Emitter()); + readonly onUnload = this._onUnload.event; private readonly _onWillDispose = this._register(new Emitter()); readonly onWillDispose = this._onWillDispose.event; @@ -80,8 +86,8 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { } private registerListeners(): void { - this._register(addDisposableListener(this.window, EventType.BEFORE_UNLOAD, (e: BeforeUnloadEvent) => this.onBeforeUnload(e))); - this._register(addDisposableListener(this.window, EventType.UNLOAD, () => this._onWillClose.fire())); + this._register(addDisposableListener(this.window, EventType.BEFORE_UNLOAD, (e: BeforeUnloadEvent) => this.handleBeforeUnload(e))); + this._register(addDisposableListener(this.window, EventType.UNLOAD, () => this.handleUnload())); this._register(addDisposableListener(this.window, 'unhandledrejection', e => { onUnexpectedError(e.reason); @@ -108,7 +114,12 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { } } - private onBeforeUnload(e: BeforeUnloadEvent): void { + private handleBeforeUnload(e: BeforeUnloadEvent): void { + + // Event + this._onBeforeUnload.fire(); + + // Check for confirm before close setting const confirmBeforeCloseSetting = this.configurationService.getValue<'always' | 'never' | 'keyboardOnly'>('window.confirmBeforeClose'); const confirmBeforeClose = confirmBeforeCloseSetting === 'always' || (confirmBeforeCloseSetting === 'keyboardOnly' && ModifierKeyEmitter.getInstance().isModifierPressed); if (confirmBeforeClose) { @@ -121,6 +132,12 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { e.returnValue = localize('lifecycleVeto', "Changes that you made may not be saved. Please check press 'Cancel' and try again."); } + private handleUnload(): void { + + // Event + this._onUnload.fire(); + } + layout(): void { this._onDidLayout.fire(getClientArea(this.window.document.body, this.container)); } @@ -224,8 +241,8 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili const height = Math.max(options?.bounds?.height ?? BrowserAuxiliaryWindowService.DEFAULT_SIZE.height, WindowMinimumSize.HEIGHT); let newWindowBounds: IRectangle = { - x: options?.bounds?.x ?? (activeWindowBounds.x + activeWindowBounds.width / 2 - width / 2), - y: options?.bounds?.y ?? (activeWindowBounds.y + activeWindowBounds.height / 2 - height / 2), + x: options?.bounds?.x ?? Math.max(activeWindowBounds.x + activeWindowBounds.width / 2 - width / 2, 0), + y: options?.bounds?.y ?? Math.max(activeWindowBounds.y + activeWindowBounds.height / 2 - height / 2, 0), width, height }; diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 854441c1122..06675aace81 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -94,7 +94,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Editor & group changes if (this.editorGroupsContainer === this.editorGroupService.mainPart || this.editorGroupsContainer === this.editorGroupService) { - this.editorGroupService.mainPart.whenReady.then(() => this.onEditorGroupsReady()); + this.editorGroupService.whenReady.then(() => this.onEditorGroupsReady()); } else { this.onEditorGroupsReady(); } diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index a0d5a1bafb1..3ab10751b59 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -221,6 +221,42 @@ export interface IEditorGroupsContainer { */ readonly onDidChangeGroupMaximized: Event; + /** + * A property that indicates when groups have been created + * and are ready to be used in the editor part. + */ + readonly isReady: boolean; + + /** + * A promise that resolves when groups have been created + * and are ready to be used in the editor part. + * + * Await this promise to safely work on the editor groups model + * (for example, install editor group listeners). + * + * Use the `whenRestored` property to await visible editors + * having fully resolved. + */ + readonly whenReady: Promise; + + /** + * A promise that resolves when groups have been restored in + * the editor part. + * + * For groups with active editor, the promise will resolve + * when the visible editor has finished to resolve. + * + * Use the `whenReady` property to not await editors to + * resolve. + */ + readonly whenRestored: Promise; + + /** + * Find out if the editor part has UI state to restore + * from a previous session. + */ + readonly hasRestorableState: boolean; + /** * An active group is the default location for new editors to open. */ @@ -412,42 +448,6 @@ export interface IEditorPart extends IEditorGroupsContainer { */ readonly contentDimension: IDimension; - /** - * A property that indicates when groups have been created - * and are ready to be used in the editor part. - */ - readonly isReady: boolean; - - /** - * A promise that resolves when groups have been created - * and are ready to be used in the editor part. - * - * Await this promise to safely work on the editor groups model - * (for example, install editor group listeners). - * - * Use the `whenRestored` property to await visible editors - * having fully resolved. - */ - readonly whenReady: Promise; - - /** - * A promise that resolves when groups have been restored in - * the editor part. - * - * For groups with active editor, the promise will resolve - * when the visible editor has finished to resolve. - * - * Use the `whenReady` property to not await editors to - * resolve. - */ - readonly whenRestored: Promise; - - /** - * Find out if the editor part has UI state to restore - * from a previous session. - */ - readonly hasRestorableState: boolean; - /** * Find out if an editor group is currently maximized. */ diff --git a/src/vs/workbench/services/history/browser/historyService.ts b/src/vs/workbench/services/history/browser/historyService.ts index d417eacada2..18062624a78 100644 --- a/src/vs/workbench/services/history/browser/historyService.ts +++ b/src/vs/workbench/services/history/browser/historyService.ts @@ -939,11 +939,11 @@ export class HistoryService extends Disposable implements IHistoryService { // We want to seed history from opened editors // too as well as previous stored state, so we // need to wait for the editor groups being ready - if (this.editorGroupService.mainPart.isReady) { + if (this.editorGroupService.isReady) { this.loadHistory(); } else { (async () => { - await this.editorGroupService.mainPart.whenReady; + await this.editorGroupService.whenReady; this.loadHistory(); })(); From 6bda74d83aaea9ae13c737629e4d23fd34a0e0ac Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 22 Dec 2023 15:40:32 +0100 Subject: [PATCH 14/26] aux window - track fullscreen state per window (#197325) --- src/vs/base/browser/browser.ts | 57 +++++++++++-------- src/vs/base/browser/ui/menu/menubar.ts | 3 +- .../browser/actions/layoutActions.ts | 4 +- .../browser/actions/windowActions.ts | 4 +- src/vs/workbench/browser/contextkeys.ts | 13 +++-- src/vs/workbench/browser/layout.ts | 55 +++++++++--------- .../parts/editor/auxiliaryEditorPart.ts | 6 +- .../browser/parts/titlebar/menubarControl.ts | 12 ++-- src/vs/workbench/browser/web.main.ts | 2 +- src/vs/workbench/browser/window.ts | 33 ++++++----- src/vs/workbench/common/contextkeys.ts | 2 +- .../contrib/splash/browser/partsSplash.ts | 4 +- .../electron-sandbox/desktop.main.ts | 2 +- src/vs/workbench/electron-sandbox/window.ts | 10 ++-- .../browser/auxiliaryWindowService.ts | 9 ++- .../auxiliaryWindowService.ts | 13 +++-- .../keybinding/browser/keybindingService.ts | 10 +++- .../services/layout/browser/layoutService.ts | 5 -- src/vs/workbench/test/browser/window.test.ts | 3 +- .../test/browser/workbenchTestServices.ts | 1 - 20 files changed, 139 insertions(+), 109 deletions(-) diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index de37d415e03..64b0984693f 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -3,21 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $window } from 'vs/base/browser/window'; +import { $window, CodeWindow } from 'vs/base/browser/window'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, markAsSingleton } from 'vs/base/common/lifecycle'; class WindowManager { - public static readonly INSTANCE = new WindowManager(); + static readonly INSTANCE = new WindowManager(); // --- Zoom Level + private _zoomLevel: number = 0; - public getZoomLevel(): number { + getZoomLevel(): number { return this._zoomLevel; } - public setZoomLevel(zoomLevel: number): void { + setZoomLevel(zoomLevel: number): void { if (this._zoomLevel === zoomLevel) { return; } @@ -25,30 +26,38 @@ class WindowManager { } // --- Zoom Factor + private _zoomFactor: number = 1; - public getZoomFactor(): number { + getZoomFactor(): number { return this._zoomFactor; } - public setZoomFactor(zoomFactor: number): void { + setZoomFactor(zoomFactor: number): void { this._zoomFactor = zoomFactor; } // --- Fullscreen - private _fullscreen: boolean = false; - private readonly _onDidChangeFullscreen = new Emitter(); - public readonly onDidChangeFullscreen: Event = this._onDidChangeFullscreen.event; - public setFullscreen(fullscreen: boolean): void { - if (this._fullscreen === fullscreen) { + private readonly _onDidChangeFullscreen = new Emitter(); + readonly onDidChangeFullscreen = this._onDidChangeFullscreen.event; + + private readonly mapWindowIdToFullScreen = new Map(); + + setFullscreen(fullscreen: boolean, targetWindow: Window): void { + if (this.isFullscreen(targetWindow) === fullscreen) { return; } - this._fullscreen = fullscreen; - this._onDidChangeFullscreen.fire(); + const windowId = this.getWindowId(targetWindow); + this.mapWindowIdToFullScreen.set(windowId, fullscreen); + this._onDidChangeFullscreen.fire(windowId); } - public isFullscreen(): boolean { - return this._fullscreen; + isFullscreen(targetWindow: Window): boolean { + return !!this.mapWindowIdToFullScreen.get(this.getWindowId(targetWindow)); + } + + private getWindowId(targetWindow: Window): number { + return (targetWindow as CodeWindow).vscodeWindowId; } } @@ -58,7 +67,7 @@ class WindowManager { class DevicePixelRatioMonitor extends Disposable { private readonly _onDidChange = this._register(new Emitter()); - public readonly onDidChange = this._onDidChange.event; + readonly onDidChange = this._onDidChange.event; private readonly _listener: () => void; private _mediaQueryList: MediaQueryList | null; @@ -86,11 +95,11 @@ class DevicePixelRatioMonitor extends Disposable { class PixelRatioImpl extends Disposable { private readonly _onDidChange = this._register(new Emitter()); - public readonly onDidChange = this._onDidChange.event; + readonly onDidChange = this._onDidChange.event; private _value: number; - public get value(): number { + get value(): number { return this._value; } @@ -131,14 +140,14 @@ class PixelRatioFacade { /** * Get the current value. */ - public get value(): number { + get value(): number { return this._getOrCreatePixelRatioMonitor().value; } /** * Listen for changes. */ - public get onDidChange(): Event { + get onDidChange(): Event { return this._getOrCreatePixelRatioMonitor().onDidChange; } } @@ -175,11 +184,11 @@ export function setZoomFactor(zoomFactor: number): void { WindowManager.INSTANCE.setZoomFactor(zoomFactor); } -export function setFullscreen(fullscreen: boolean): void { - WindowManager.INSTANCE.setFullscreen(fullscreen); +export function setFullscreen(fullscreen: boolean, targetWindow: Window): void { + WindowManager.INSTANCE.setFullscreen(fullscreen, targetWindow); } -export function isFullscreen(): boolean { - return WindowManager.INSTANCE.isFullscreen(); +export function isFullscreen(targetWindow: Window): boolean { + return WindowManager.INSTANCE.isFullscreen(targetWindow); } export const onDidChangeFullscreen = WindowManager.INSTANCE.onDidChangeFullscreen; diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 2e58738bdab..feadeb47651 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -22,6 +22,7 @@ import { isMacintosh } from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import 'vs/css!./menubar'; import * as nls from 'vs/nls'; +import { mainWindow } from 'vs/base/browser/window'; const $ = DOM.$; @@ -786,7 +787,7 @@ export class MenuBar extends Disposable { private setUnfocusedState(): void { if (this.options.visibility === 'toggle' || this.options.visibility === 'hidden') { this.focusState = MenubarState.HIDDEN; - } else if (this.options.visibility === 'classic' && browser.isFullscreen()) { + } else if (this.options.visibility === 'classic' && browser.isFullscreen(mainWindow)) { this.focusState = MenubarState.HIDDEN; } else { this.focusState = MenubarState.VISIBLE; diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index a3bacf4ad5c..a8df6a29409 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -21,7 +21,7 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { ToggleAuxiliaryBarAction } from 'vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions'; import { TogglePanelAction } from 'vs/workbench/browser/parts/panel/panelActions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { AuxiliaryBarVisibleContext, PanelAlignmentContext, PanelVisibleContext, SideBarVisibleContext, FocusedViewContext, InEditorZenModeContext, IsCenteredLayoutContext, MainEditorAreaVisibleContext, IsFullscreenContext, PanelPositionContext, IsAuxiliaryWindowFocusedContext } from 'vs/workbench/common/contextkeys'; +import { AuxiliaryBarVisibleContext, PanelAlignmentContext, PanelVisibleContext, SideBarVisibleContext, FocusedViewContext, InEditorZenModeContext, IsCenteredLayoutContext, MainEditorAreaVisibleContext, IsMainWindowFullscreenContext, PanelPositionContext, IsAuxiliaryWindowFocusedContext } from 'vs/workbench/common/contextkeys'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -1387,7 +1387,7 @@ const AlignPanelActions: CustomizeLayoutItem[] = [ ]; const MiscLayoutOptions: CustomizeLayoutItem[] = [ - CreateOptionLayoutItem('workbench.action.toggleFullScreen', IsFullscreenContext, localize('fullscreen', "Full Screen"), fullscreenIcon), + CreateOptionLayoutItem('workbench.action.toggleFullScreen', IsMainWindowFullscreenContext, localize('fullscreen', "Full Screen"), fullscreenIcon), CreateOptionLayoutItem('workbench.action.toggleZenMode', InEditorZenModeContext, localize('zenMode', "Zen Mode"), zenModeIcon), CreateOptionLayoutItem('workbench.action.toggleCenteredLayout', IsCenteredLayoutContext, localize('centeredLayout', "Centered Layout"), centerLayoutIcon), ]; diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index 14c7b8aa01f..24aefca0450 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -8,7 +8,7 @@ import { IWindowOpenable } from 'vs/platform/window/common/window'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { MenuRegistry, MenuId, Action2, registerAction2, IAction2Options } from 'vs/platform/actions/common/actions'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IsAuxiliaryWindowFocusedContext, IsFullscreenContext } from 'vs/workbench/common/contextkeys'; +import { IsAuxiliaryWindowFocusedContext, IsMainWindowFullscreenContext } from 'vs/workbench/common/contextkeys'; import { IsMacNativeContext, IsDevelopmentContext, IsWebContext, IsIOSContext } from 'vs/platform/contextkey/common/contextkeys'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -298,7 +298,7 @@ class ToggleFullScreenAction extends Action2 { } }, precondition: IsIOSContext.toNegated(), - toggled: IsFullscreenContext, + toggled: IsMainWindowFullscreenContext, menu: [{ id: MenuId.MenubarAppearanceMenu, group: '1_toggle_view', diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index 4c142fb8246..c10da3b722c 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from 'vs/platform/contextkey/common/contextkeys'; -import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, MainEditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds, TitleBarVisibleContext, TitleBarStyleContext, MultipleEditorGroupsContext, IsAuxiliaryWindowFocusedContext, ActiveCompareEditorOriginalWriteableContext } from 'vs/workbench/common/contextkeys'; +import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, MainEditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsMainWindowFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds, TitleBarVisibleContext, TitleBarStyleContext, MultipleEditorGroupsContext, IsAuxiliaryWindowFocusedContext, ActiveCompareEditorOriginalWriteableContext } from 'vs/workbench/common/contextkeys'; import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType, onDidRegisterWindow, getActiveWindow } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -28,6 +28,7 @@ import { FileSystemProviderCapabilities, IFileService } from 'vs/platform/files/ import { getTitleBarStyle } from 'vs/platform/window/common/window'; import { mainWindow } from 'vs/base/browser/window'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; +import { isFullscreen, onDidChangeFullscreen } from 'vs/base/browser/browser'; export class WorkbenchContextKeysHandler extends Disposable { private inputFocusedContext: IContextKey; @@ -68,7 +69,7 @@ export class WorkbenchContextKeysHandler extends Disposable { private temporaryWorkspaceContext: IContextKey; private inZenModeContext: IContextKey; - private isFullscreenContext: IContextKey; + private isMainWindowFullscreenContext: IContextKey; private isAuxiliaryWindowFocusedContext: IContextKey; private isCenteredLayoutContext: IContextKey; private sideBarVisibleContext: IContextKey; @@ -188,7 +189,7 @@ export class WorkbenchContextKeysHandler extends Disposable { this.updateSplitEditorsVerticallyContext(); // Window - this.isFullscreenContext = IsFullscreenContext.bindTo(this.contextKeyService); + this.isMainWindowFullscreenContext = IsMainWindowFullscreenContext.bindTo(this.contextKeyService); this.isAuxiliaryWindowFocusedContext = IsAuxiliaryWindowFocusedContext.bindTo(this.contextKeyService); // Zen Mode @@ -260,7 +261,11 @@ export class WorkbenchContextKeysHandler extends Disposable { this._register(this.layoutService.onDidChangeZenMode(enabled => this.inZenModeContext.set(enabled))); this._register(this.layoutService.onDidChangeActiveContainer(() => this.isAuxiliaryWindowFocusedContext.set(this.layoutService.activeContainer !== this.layoutService.mainContainer))); - this._register(this.layoutService.onDidChangeFullscreen(fullscreen => this.isFullscreenContext.set(fullscreen))); + this._register(onDidChangeFullscreen(windowId => { + if (windowId === mainWindow.vscodeWindowId) { + this.isMainWindowFullscreenContext.set(isFullscreen(mainWindow)); + } + })); this._register(this.layoutService.onDidChangeCenteredLayout(centered => this.isCenteredLayoutContext.set(centered))); this._register(this.layoutService.onDidChangePanelPosition(position => this.panelPositionContext.set(position))); diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 4d64eeb391c..bef47d88deb 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -53,7 +53,7 @@ import { mainWindow } from 'vs/base/browser/window'; interface ILayoutRuntimeState { activeContainerId: number; - fullscreen: boolean; + mainWindowFullscreen: boolean; readonly maximized: Set; hasFocus: boolean; mainWindowBorder: boolean; @@ -125,9 +125,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private readonly _onDidChangeZenMode = this._register(new Emitter()); readonly onDidChangeZenMode = this._onDidChangeZenMode.event; - private readonly _onDidChangeFullscreen = this._register(new Emitter()); - readonly onDidChangeFullscreen = this._onDidChangeFullscreen.event; - private readonly _onDidChangeCenteredLayout = this._register(new Emitter()); readonly onDidChangeCenteredLayout = this._onDidChangeCenteredLayout.event; @@ -358,7 +355,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi })); // Fullscreen changes - this._register(onDidChangeFullscreen(() => this.onFullscreenChanged())); + this._register(onDidChangeFullscreen(windowId => this.onFullscreenChanged(windowId))); // Group changes this._register(this.editorGroupService.mainPart.onDidAddGroup(() => this.centerMainEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED)))); @@ -408,7 +405,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // The menu bar toggles the title bar in full screen for toggle and classic settings - else if (this.state.runtime.fullscreen && (menuBarVisibility === 'toggle' || menuBarVisibility === 'classic')) { + else if (this.state.runtime.mainWindowFullscreen && (menuBarVisibility === 'toggle' || menuBarVisibility === 'classic')) { this.workbenchGrid.setViewVisible(this.titleBarPartView, this.shouldShowTitleBar()); } @@ -431,11 +428,15 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._onDidLayoutContainer.fire({ container, dimension }); } - private onFullscreenChanged(): void { - this.state.runtime.fullscreen = isFullscreen(); + private onFullscreenChanged(windowId: number): void { + if (windowId !== mainWindow.vscodeWindowId) { + return; // ignore all but main window + } + + this.state.runtime.mainWindowFullscreen = isFullscreen(mainWindow); // Apply as CSS class - if (this.state.runtime.fullscreen) { + if (this.state.runtime.mainWindowFullscreen) { this.mainContainer.classList.add(LayoutClasses.FULLSCREEN); } else { this.mainContainer.classList.remove(LayoutClasses.FULLSCREEN); @@ -448,9 +449,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // Change edge snapping accordingly - this.workbenchGrid.edgeSnapping = this.state.runtime.fullscreen; + this.workbenchGrid.edgeSnapping = this.state.runtime.mainWindowFullscreen; - // Changing fullscreen state of the window has an impact + // Changing fullscreen state of the main window has an impact // on custom title bar visibility, so we need to update if (getTitleBarStyle(this.configurationService) === 'custom') { @@ -459,8 +460,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.updateWindowsBorder(true); } - - this._onDidChangeFullscreen.fire(this.state.runtime.fullscreen); } private onActiveWindowChanged(): void { @@ -554,7 +553,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const containerWindowId = getWindowId(getWindow(container)); let windowBorder = false; - if (!this.state.runtime.fullscreen && !this.state.runtime.maximized.has(containerWindowId) && (activeBorder || inactiveBorder)) { + if (!this.state.runtime.mainWindowFullscreen && !this.state.runtime.maximized.has(containerWindowId) && (activeBorder || inactiveBorder)) { windowBorder = true; // If the inactive color is missing, fallback to the active one @@ -629,7 +628,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Layout Runtime State const layoutRuntimeState: ILayoutRuntimeState = { activeContainerId: this.getActiveContainerId(), - fullscreen: isFullscreen(), + mainWindowFullscreen: isFullscreen(mainWindow), hasFocus: this.hostService.hasFocus, maximized: new Set(), mainWindowBorder: false, @@ -1237,23 +1236,23 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // macOS desktop does not need a title bar when full screen if (isMacintosh && isNative) { - return !this.state.runtime.fullscreen; + return !this.state.runtime.mainWindowFullscreen; } // non-fullscreen native must show the title bar - if (isNative && !this.state.runtime.fullscreen) { + if (isNative && !this.state.runtime.mainWindowFullscreen) { return true; } // if WCO is visible, we have to show the title bar - if (isWCOEnabled() && !this.state.runtime.fullscreen) { + if (isWCOEnabled() && !this.state.runtime.mainWindowFullscreen) { return true; } // remaining behavior is based on menubar visibility switch (getMenuBarVisibility(this.configurationService)) { case 'classic': - return !this.state.runtime.fullscreen || this.state.runtime.menuBar.toggled; + return !this.state.runtime.mainWindowFullscreen || this.state.runtime.menuBar.toggled; case 'compact': case 'hidden': return false; @@ -1262,7 +1261,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi case 'visible': return true; default: - return isWeb ? false : !this.state.runtime.fullscreen || this.state.runtime.menuBar.toggled; + return isWeb ? false : !this.state.runtime.mainWindowFullscreen || this.state.runtime.menuBar.toggled; } } @@ -1336,17 +1335,17 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Check if zen mode transitioned to full screen and if now we are out of zen mode // -> we need to go out of full screen (same goes for the centered editor layout) - let toggleFullScreen = false; + let toggleMainWindowFullScreen = false; const config = getZenModeConfiguration(this.configurationService); const zenModeExitInfo = this.stateModel.getRuntimeValue(LayoutStateKeys.ZEN_MODE_EXIT_INFO); // Zen Mode Active if (this.stateModel.getRuntimeValue(LayoutStateKeys.ZEN_MODE_ACTIVE)) { - toggleFullScreen = !this.state.runtime.fullscreen && config.fullScreen && !isIOS; + toggleMainWindowFullScreen = !this.state.runtime.mainWindowFullscreen && config.fullScreen && !isIOS; if (!restoring) { - zenModeExitInfo.transitionedToFullScreen = toggleFullScreen; + zenModeExitInfo.transitionedToFullScreen = toggleMainWindowFullScreen; zenModeExitInfo.transitionedToCenteredEditorLayout = !this.isMainEditorLayoutCentered() && config.centerLayout; zenModeExitInfo.handleNotificationsDoNotDisturbMode = this.notificationService.getFilter() === NotificationsFilter.OFF; zenModeExitInfo.wasVisible.sideBar = this.isVisible(Parts.SIDEBAR_PART); @@ -1461,15 +1460,15 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.focus(); - toggleFullScreen = zenModeExitInfo.transitionedToFullScreen && this.state.runtime.fullscreen; + toggleMainWindowFullScreen = zenModeExitInfo.transitionedToFullScreen && this.state.runtime.mainWindowFullscreen; } if (!skipLayout) { this.layout(); } - if (toggleFullScreen) { - this.hostService.toggleFullScreen(getActiveWindow()); + if (toggleMainWindowFullScreen) { + this.hostService.toggleFullScreen(mainWindow); } // Event @@ -1531,7 +1530,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.mainContainer.prepend(workbenchGrid.element); this.mainContainer.setAttribute('role', 'application'); this.workbenchGrid = workbenchGrid; - this.workbenchGrid.edgeSnapping = this.state.runtime.fullscreen; + this.workbenchGrid.edgeSnapping = this.state.runtime.mainWindowFullscreen; for (const part of [titleBar, editorPart, activityBar, panelPart, sideBar, statusBar, auxiliaryBarPart, bannerPart]) { this._register(part.onDidVisibilityChange((visible) => { @@ -1738,7 +1737,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi !this.isVisible(Parts.PANEL_PART) ? LayoutClasses.PANEL_HIDDEN : undefined, !this.isVisible(Parts.AUXILIARYBAR_PART) ? LayoutClasses.AUXILIARYBAR_HIDDEN : undefined, !this.isVisible(Parts.STATUSBAR_PART) ? LayoutClasses.STATUSBAR_HIDDEN : undefined, - this.state.runtime.fullscreen ? LayoutClasses.FULLSCREEN : undefined + this.state.runtime.mainWindowFullscreen ? LayoutClasses.FULLSCREEN : undefined ]); } diff --git a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts index e6322146585..272979d0555 100644 --- a/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts +++ b/src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { onDidChangeFullscreen } from 'vs/base/browser/browser'; import { detectFullscreen, hide, show } from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -49,8 +50,7 @@ export class AuxiliaryEditorPart { @IConfigurationService private readonly configurationService: IConfigurationService, @IStatusbarService private readonly statusbarService: IStatusbarService, @ITitleService private readonly titleService: ITitleService, - @IEditorService private readonly editorService: IEditorService, - @IHostService private readonly hostService: IHostService + @IEditorService private readonly editorService: IEditorService ) { } @@ -114,7 +114,7 @@ export class AuxiliaryEditorPart { disposables.add(titlebarPart.onDidChange(() => updateEditorPartHeight(true))); - disposables.add(this.hostService.onDidChangeFullScreen(windowId => { + disposables.add(onDidChangeFullscreen(windowId => { if (windowId !== auxiliaryWindow.window.vscodeWindowId) { return; // ignore all but our window } diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 46c8de7dbd7..61bd34e2889 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -28,8 +28,7 @@ import { MenuBar, IMenuBarOptions } from 'vs/base/browser/ui/menu/menubar'; import { Direction } from 'vs/base/browser/ui/menu/menu'; import { mnemonicMenuLabel, unmnemonicLabel } from 'vs/base/common/labels'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { isFullscreen } from 'vs/base/browser/browser'; +import { isFullscreen, onDidChangeFullscreen } from 'vs/base/browser/browser'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { BrowserFeatures } from 'vs/base/browser/canIUse'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -398,7 +397,6 @@ export class CustomMenubarControl extends MenubarControl { @IPreferencesService preferencesService: IPreferencesService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IAccessibilityService accessibilityService: IAccessibilityService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IHostService hostService: IHostService, @ICommandService commandService: ICommandService @@ -535,7 +533,7 @@ export class CustomMenubarControl extends MenubarControl { enableMenuBarMnemonics = true; } - return enableMenuBarMnemonics && (!isWeb || isFullscreen()); + return enableMenuBarMnemonics && (!isWeb || isFullscreen(mainWindow)); } private get currentCompactMenuMode(): Direction | undefined { @@ -798,7 +796,11 @@ export class CustomMenubarControl extends MenubarControl { // Mnemonics require fullscreen in web if (isWeb) { - this._register(this.layoutService.onDidChangeFullscreen(e => this.updateMenubar())); + this._register(onDidChangeFullscreen(windowId => { + if (windowId === mainWindow.vscodeWindowId) { + this.updateMenubar(); + } + })); this._register(this.webNavigationMenu.onDidChange(() => this.updateMenubar())); } } diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 83800a7036d..c49eec92da6 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -113,7 +113,7 @@ export class BrowserMain extends Disposable { private init(): void { // Browser config - setFullscreen(!!detectFullscreen(mainWindow)); + setFullscreen(!!detectFullscreen(mainWindow), mainWindow); } async open(): Promise { diff --git a/src/vs/workbench/browser/window.ts b/src/vs/workbench/browser/window.ts index 56278f29851..f0db3ec3276 100644 --- a/src/vs/workbench/browser/window.ts +++ b/src/vs/workbench/browser/window.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { isSafari, setFullscreen } from 'vs/base/browser/browser'; -import { addDisposableListener, detectFullscreen, EventHelper, EventType, getActiveWindow, getWindow, getWindows, getWindowsCount, windowOpenNoOpener, windowOpenPopup, windowOpenWithSuccess } from 'vs/base/browser/dom'; +import { addDisposableListener, detectFullscreen, EventHelper, EventType, getActiveWindow, getWindow, getWindowById, getWindows, getWindowsCount, windowOpenNoOpener, windowOpenPopup, windowOpenWithSuccess } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { HidDeviceData, requestHidDevice, requestSerialPort, requestUsbDevice, SerialPortData, UsbDeviceData } from 'vs/base/browser/deviceAccess'; import { timeout } from 'vs/base/common/async'; @@ -35,11 +35,17 @@ export abstract class BaseWindow extends Disposable { private static TIMEOUT_HANDLES = Number.MIN_SAFE_INTEGER; // try to not compete with the IDs of native `setTimeout` private static readonly TIMEOUT_DISPOSABLES = new Map>(); - constructor(targetWindow: CodeWindow, dom = { getWindowsCount, getWindows } /* for testing */) { + constructor( + targetWindow: CodeWindow, + dom = { getWindowsCount, getWindows }, /* for testing */ + @IHostService protected readonly hostService: IHostService + ) { super(); this.enableWindowFocusOnElementFocus(targetWindow); this.enableMultiWindowAwareTimeout(targetWindow, dom); + + this.registerFullScreenListeners(targetWindow.vscodeWindowId); } //#region focus handling in multi-window applications @@ -128,6 +134,16 @@ export abstract class BaseWindow extends Disposable { //#endregion + private registerFullScreenListeners(targetWindowId: number): void { + this._register(this.hostService.onDidChangeFullScreen(windowId => { + if (windowId === targetWindowId) { + const targetWindow = getWindowById(targetWindowId); + if (targetWindow) { + setFullscreen(!!detectFullscreen(targetWindow.window), targetWindow.window); + } + } + })); + } } export class BrowserWindow extends BaseWindow { @@ -141,9 +157,9 @@ export class BrowserWindow extends BaseWindow { @IBrowserWorkbenchEnvironmentService private readonly environmentService: IBrowserWorkbenchEnvironmentService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IHostService private readonly hostService: IHostService + @IHostService hostService: IHostService ) { - super(mainWindow); + super(mainWindow, undefined, hostService); this.registerListeners(); this.create(); @@ -173,15 +189,6 @@ export class BrowserWindow extends BaseWindow { // Prevent default navigation on drop this._register(addDisposableListener(this.layoutService.mainContainer, EventType.DROP, e => EventHelper.stop(e, true))); - - // Fullscreen - this._register(this.hostService.onDidChangeFullScreen(windowId => { - if (windowId !== mainWindow.vscodeWindowId) { - return; // ignore all but main window - } - - setFullscreen(!!detectFullscreen(mainWindow)); - })); } private onWillShutdown(): void { diff --git a/src/vs/workbench/common/contextkeys.ts b/src/vs/workbench/common/contextkeys.ts index fc4a59cb05e..b778b1689df 100644 --- a/src/vs/workbench/common/contextkeys.ts +++ b/src/vs/workbench/common/contextkeys.ts @@ -33,7 +33,7 @@ export const RemoteNameContext = new RawContextKey('remoteName', '', loc export const VirtualWorkspaceContext = new RawContextKey('virtualWorkspace', '', localize('virtualWorkspace', "The scheme of the current workspace is from a virtual file system or an empty string.")); export const TemporaryWorkspaceContext = new RawContextKey('temporaryWorkspace', false, localize('temporaryWorkspace', "The scheme of the current workspace is from a temporary file system.")); -export const IsFullscreenContext = new RawContextKey('isFullscreen', false, localize('isFullscreen', "Whether the window is in fullscreen mode")); +export const IsMainWindowFullscreenContext = new RawContextKey('isFullscreen', false, localize('isFullscreen', "Whether the main window is in fullscreen mode")); export const IsAuxiliaryWindowFocusedContext = new RawContextKey('isAuxiliaryWindowFocusedContext', false, localize('isAuxiliaryWindowFocusedContext', "Whether an auxiliary window is focused")); export const HasWebFileSystemAccess = new RawContextKey('hasWebFileSystemAccess', false, true); // Support for FileSystemAccess web APIs (https://wicg.github.io/file-system-access) diff --git a/src/vs/workbench/contrib/splash/browser/partsSplash.ts b/src/vs/workbench/contrib/splash/browser/partsSplash.ts index 1007270a967..63f2d79012d 100644 --- a/src/vs/workbench/contrib/splash/browser/partsSplash.ts +++ b/src/vs/workbench/contrib/splash/browser/partsSplash.ts @@ -49,7 +49,7 @@ export class PartsSplash { lastIdleSchedule.value = dom.runWhenWindowIdle(mainWindow, () => this._savePartsSplash(), 2500); }; lifecycleService.when(LifecyclePhase.Restored).then(() => { - Event.any(onDidChangeFullscreen, editorGroupsService.mainPart.onDidLayout, _themeService.onDidColorThemeChange)(savePartsSplashSoon, undefined, this._disposables); + Event.any(Event.filter(onDidChangeFullscreen, windowId => windowId === mainWindow.vscodeWindowId), editorGroupsService.mainPart.onDidLayout, _themeService.onDidColorThemeChange)(savePartsSplashSoon, undefined, this._disposables); savePartsSplashSoon(); }); @@ -96,7 +96,7 @@ export class PartsSplash { } private _shouldSaveLayoutInfo(): boolean { - return !isFullscreen() && !this._environmentService.isExtensionDevelopment && !this._didChangeTitleBarStyle; + return !isFullscreen(mainWindow) && !this._environmentService.isExtensionDevelopment && !this._didChangeTitleBarStyle; } private _removePartsSplash(): void { diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index b285e2ed236..0acf57e3448 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -78,7 +78,7 @@ export class DesktopMain extends Disposable { this.reviveUris(); // Apply fullscreen early if configured - setFullscreen(!!this.configuration.fullscreen); + setFullscreen(!!this.configuration.fullscreen, mainWindow); } private reviveUris() { diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index b1388d0b6a6..a0df8c476f2 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -72,6 +72,7 @@ import { IUtilityProcessWorkerWorkbenchService } from 'vs/workbench/services/uti import { registerWindowDriver } from 'vs/workbench/services/driver/electron-sandbox/driver'; import { mainWindow } from 'vs/base/browser/window'; import { BaseWindow } from 'vs/workbench/browser/window'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export class NativeWindow extends BaseWindow { @@ -124,9 +125,10 @@ export class NativeWindow extends BaseWindow { @IBannerService private readonly bannerService: IBannerService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @IPreferencesService private readonly preferencesService: IPreferencesService, - @IUtilityProcessWorkerWorkbenchService private readonly utilityProcessWorkerWorkbenchService: IUtilityProcessWorkerWorkbenchService + @IUtilityProcessWorkerWorkbenchService private readonly utilityProcessWorkerWorkbenchService: IUtilityProcessWorkerWorkbenchService, + @IHostService hostService: IHostService ) { - super(mainWindow); + super(mainWindow, undefined, hostService); this.mainPartEditorService = editorService.createScoped('main', this._store); @@ -248,8 +250,8 @@ export class NativeWindow extends BaseWindow { }); // Fullscreen Events - ipcRenderer.on('vscode:enterFullScreen', async () => { setFullscreen(true); }); - ipcRenderer.on('vscode:leaveFullScreen', async () => { setFullscreen(false); }); + ipcRenderer.on('vscode:enterFullScreen', async () => { setFullscreen(true, mainWindow); }); + ipcRenderer.on('vscode:leaveFullScreen', async () => { setFullscreen(false, mainWindow); }); // Proxy Login Dialog ipcRenderer.on('vscode:openProxyAuthenticationDialog', async (event: unknown, payload: { authInfo: AuthInfo; username?: string; password?: string; replyChannel: string }) => { diff --git a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts index bcb5c79f51b..5af8ccad75a 100644 --- a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts @@ -21,6 +21,7 @@ import { BaseWindow } from 'vs/workbench/browser/window'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Barrier } from 'vs/base/common/async'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export const IAuxiliaryWindowService = createDecorator('auxiliaryWindowService'); @@ -78,8 +79,9 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { readonly container: HTMLElement, stylesHaveLoaded: Barrier, @IConfigurationService private readonly configurationService: IConfigurationService, + @IHostService hostService: IHostService ) { - super(window); + super(window, undefined, hostService); this.whenStylesHaveLoaded = stylesHaveLoaded.wait().then(() => { }); this.registerListeners(); @@ -170,7 +172,8 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IDialogService private readonly dialogService: IDialogService, @IConfigurationService protected readonly configurationService: IConfigurationService, - @ITelemetryService private readonly telemetryService: ITelemetryService + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IHostService protected readonly hostService: IHostService ) { super(); } @@ -225,7 +228,7 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili } protected createAuxiliaryWindow(targetWindow: CodeWindow, container: HTMLElement, stylesLoaded: Barrier): AuxiliaryWindow { - return new AuxiliaryWindow(targetWindow, container, stylesLoaded, this.configurationService); + return new AuxiliaryWindow(targetWindow, container, stylesLoaded, this.configurationService, this.hostService); } private async openWindow(options?: IAuxiliaryWindowOpenOptions): Promise { diff --git a/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts index 525eb74fd2a..3ac7684e596 100644 --- a/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts @@ -20,6 +20,7 @@ import { ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Barrier } from 'vs/base/common/async'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; type NativeCodeWindow = CodeWindow & { readonly vscode: ISandboxGlobals; @@ -35,9 +36,10 @@ export class NativeAuxiliaryWindow extends AuxiliaryWindow { stylesHaveLoaded: Barrier, @IConfigurationService configurationService: IConfigurationService, @INativeHostService private readonly nativeHostService: INativeHostService, - @IInstantiationService private readonly instantiationService: IInstantiationService + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IHostService hostService: IHostService ) { - super(window, container, stylesHaveLoaded, configurationService); + super(window, container, stylesHaveLoaded, configurationService, hostService); } protected override async confirmBeforeClose(e: BeforeUnloadEvent): Promise { @@ -65,9 +67,10 @@ export class NativeAuxiliaryWindowService extends BrowserAuxiliaryWindowService @IDialogService dialogService: IDialogService, @IInstantiationService private readonly instantiationService: IInstantiationService, @ITelemetryService telemetryService: ITelemetryService, - @IEnvironmentService private readonly environmentService: IEnvironmentService + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IHostService hostService: IHostService ) { - super(layoutService, dialogService, configurationService, telemetryService); + super(layoutService, dialogService, configurationService, telemetryService, hostService); } protected override async resolveWindowId(auxiliaryWindow: NativeCodeWindow): Promise { @@ -110,7 +113,7 @@ export class NativeAuxiliaryWindowService extends BrowserAuxiliaryWindowService } protected override createAuxiliaryWindow(targetWindow: CodeWindow, container: HTMLElement, stylesHaveLoaded: Barrier,): AuxiliaryWindow { - return new NativeAuxiliaryWindow(targetWindow, container, stylesHaveLoaded, this.configurationService, this.nativeHostService, this.instantiationService); + return new NativeAuxiliaryWindow(targetWindow, container, stylesHaveLoaded, this.configurationService, this.nativeHostService, this.instantiationService, this.hostService); } } diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index fb13ac6c764..ad218733589 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -241,14 +241,18 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { this._register(Event.runAndSubscribe(dom.onDidRegisterWindow, ({ window, disposables }) => disposables.add(this._registerKeyListeners(window)), { window: mainWindow, disposables: this._store })); - this._register(browser.onDidChangeFullscreen(() => { + this._register(browser.onDidChangeFullscreen(windowId => { + if (windowId !== mainWindow.vscodeWindowId) { + return; + } + const keyboard: IKeyboard | null = (navigator).keyboard; if (BrowserFeatures.keyboard === KeyboardSupport.None) { return; } - if (browser.isFullscreen()) { + if (browser.isFullscreen(mainWindow)) { keyboard?.lock(['Escape']); } else { keyboard?.unlock(); @@ -461,7 +465,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { return false; } - if (BrowserFeatures.keyboard === KeyboardSupport.FullScreen && browser.isFullscreen()) { + if (BrowserFeatures.keyboard === KeyboardSupport.FullScreen && browser.isFullscreen(mainWindow)) { return false; } diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index aa61afdf61c..10ee5560632 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -116,11 +116,6 @@ export interface IWorkbenchLayoutService extends ILayoutService { */ readonly onDidChangeZenMode: Event; - /** - * Emits when fullscreen is enabled or disabled. - */ - readonly onDidChangeFullscreen: Event; - /** * Emits when the target window is maximized or unmaximized. */ diff --git a/src/vs/workbench/test/browser/window.test.ts b/src/vs/workbench/test/browser/window.test.ts index eabf5661cfc..6d9b702cea6 100644 --- a/src/vs/workbench/test/browser/window.test.ts +++ b/src/vs/workbench/test/browser/window.test.ts @@ -10,6 +10,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { BaseWindow } from 'vs/workbench/browser/window'; +import { TestHostService } from 'vs/workbench/test/browser/workbenchTestServices'; suite('Window', () => { @@ -18,7 +19,7 @@ suite('Window', () => { class TestWindow extends BaseWindow { constructor(window: CodeWindow, dom: { getWindowsCount: () => number; getWindows: () => Iterable }) { - super(window, dom); + super(window, dom, new TestHostService()); } protected override enableWindowFocusOnElementFocus(): void { } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index ceabefed273..a80fd170b01 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -605,7 +605,6 @@ export class TestLayoutService implements IWorkbenchLayoutService { onDidChangeZenMode: Event = Event.None; onDidChangeCenteredLayout: Event = Event.None; - onDidChangeFullscreen: Event = Event.None; onDidChangeWindowMaximized: Event<{ windowId: number; maximized: boolean }> = Event.None; onDidChangePanelPosition: Event = Event.None; onDidChangePanelAlignment: Event = Event.None; From 37544870745f1d17ecaefecb4feaf4a7eb2448d4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 22 Dec 2023 15:51:21 +0100 Subject: [PATCH 15/26] aux window - enable reload window --- .../auxiliaryWindow/electron-main/auxiliaryWindow.ts | 2 -- src/vs/workbench/browser/actions/windowActions.ts | 8 ++------ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts index cf36f0950b9..1730e9a0817 100644 --- a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts +++ b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts @@ -39,8 +39,6 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { ) { super(configurationService, stateService, environmentMainService); - contents.removeAllListeners('devtools-reload-page'); // remove built in listener as aux windows have no reload - // Try to claim window this.tryClaimWindow(); } diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index 24aefca0450..bf3fc0d3b1b 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -8,7 +8,7 @@ import { IWindowOpenable } from 'vs/platform/window/common/window'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { MenuRegistry, MenuId, Action2, registerAction2, IAction2Options } from 'vs/platform/actions/common/actions'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IsAuxiliaryWindowFocusedContext, IsMainWindowFullscreenContext } from 'vs/workbench/common/contextkeys'; +import { IsMainWindowFullscreenContext } from 'vs/workbench/common/contextkeys'; import { IsMacNativeContext, IsDevelopmentContext, IsWebContext, IsIOSContext } from 'vs/platform/contextkey/common/contextkeys'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -35,7 +35,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { isFolderBackupInfo, isWorkspaceBackupInfo } from 'vs/platform/backup/common/backup'; import { getActiveElement, getActiveWindow } from 'vs/base/browser/dom'; -import { mainWindow } from 'vs/base/browser/window'; export const inRecentFilesPickerContextKey = 'inRecentFilesPicker'; @@ -323,7 +322,6 @@ export class ReloadWindowAction extends Action2 { id: ReloadWindowAction.ID, title: { value: localize('reloadWindow', "Reload Window"), original: 'Reload Window' }, category: Categories.Developer, - precondition: IsAuxiliaryWindowFocusedContext.toNegated(), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib + 50, @@ -336,9 +334,7 @@ export class ReloadWindowAction extends Action2 { override async run(accessor: ServicesAccessor): Promise { const hostService = accessor.get(IHostService); - if (getActiveWindow() === mainWindow) { - return hostService.reload(); // only supported for main window - } + return hostService.reload(); } } From c29a6e10b9f3db1bfd548f1fbc766ebaf5ac5796 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 24 Dec 2023 09:27:33 +0100 Subject: [PATCH 16/26] aux window - allow a per-window zoom level (#185836) --- src/vs/base/browser/browser.ts | 45 +++++---- .../browser/ui/scrollbar/scrollableElement.ts | 3 +- src/vs/code/electron-main/app.ts | 7 ++ .../issue/issueReporterService.ts | 6 +- .../processExplorer/processExplorerMain.ts | 6 +- src/vs/platform/window/common/window.ts | 1 + .../platform/window/electron-main/window.ts | 3 + .../window/electron-sandbox/window.ts | 34 +++++-- .../windows/electron-main/windowImpl.ts | 34 ++++++- .../platform/windows/electron-main/windows.ts | 2 +- .../electron-main/windowsMainService.ts | 1 - .../test/electron-main/windowsFinder.test.ts | 1 + .../browser/parts/editor/editorParts.ts | 13 ++- .../browser/parts/titlebar/titlebarPart.ts | 8 +- .../electron-sandbox/actions/windowActions.ts | 91 +++++++++++++++---- .../electron-sandbox/desktop.contribution.ts | 12 ++- .../electron-sandbox/desktop.main.ts | 17 +++- .../parts/titlebar/titlebarPart.ts | 6 +- src/vs/workbench/electron-sandbox/window.ts | 36 ++++++-- .../browser/auxiliaryWindowService.ts | 5 +- .../auxiliaryWindowService.ts | 17 +++- .../electron-sandbox/contextmenuService.ts | 2 +- .../issue/electron-sandbox/issueService.ts | 5 +- 23 files changed, 264 insertions(+), 91 deletions(-) diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index 64b0984693f..d8bd1f089ce 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -13,27 +13,33 @@ class WindowManager { // --- Zoom Level - private _zoomLevel: number = 0; + private readonly mapWindowIdToZoomLevel = new Map(); - getZoomLevel(): number { - return this._zoomLevel; + private readonly _onDidChangeZoomLevel = new Emitter(); + readonly onDidChangeZoomLevel = this._onDidChangeZoomLevel.event; + + getZoomLevel(targetWindow: Window): number { + return this.mapWindowIdToZoomLevel.get(this.getWindowId(targetWindow)) ?? 0; } - setZoomLevel(zoomLevel: number): void { - if (this._zoomLevel === zoomLevel) { + setZoomLevel(zoomLevel: number, targetWindow: Window): void { + if (this.getZoomLevel(targetWindow) === zoomLevel) { return; } - this._zoomLevel = zoomLevel; + + const targetWindowId = this.getWindowId(targetWindow); + this.mapWindowIdToZoomLevel.set(targetWindowId, zoomLevel); + this._onDidChangeZoomLevel.fire(targetWindowId); } // --- Zoom Factor - private _zoomFactor: number = 1; + private readonly mapWindowIdToZoomFactor = new Map(); - getZoomFactor(): number { - return this._zoomFactor; + getZoomFactor(targetWindow: Window): number { + return this.mapWindowIdToZoomFactor.get(this.getWindowId(targetWindow)) ?? 1; } - setZoomFactor(zoomFactor: number): void { - this._zoomFactor = zoomFactor; + setZoomFactor(zoomFactor: number, targetWindow: Window): void { + this.mapWindowIdToZoomFactor.set(this.getWindowId(targetWindow), zoomFactor); } // --- Fullscreen @@ -169,19 +175,20 @@ export function addMatchMediaChangeListener(query: string | MediaQueryList, call export const PixelRatio = new PixelRatioFacade(); /** A zoom index, e.g. 1, 2, 3 */ -export function setZoomLevel(zoomLevel: number): void { - WindowManager.INSTANCE.setZoomLevel(zoomLevel); +export function setZoomLevel(zoomLevel: number, targetWindow: Window): void { + WindowManager.INSTANCE.setZoomLevel(zoomLevel, targetWindow); } -export function getZoomLevel(): number { - return WindowManager.INSTANCE.getZoomLevel(); +export function getZoomLevel(targetWindow: Window): number { + return WindowManager.INSTANCE.getZoomLevel(targetWindow); } +export const onDidChangeZoomLevel = WindowManager.INSTANCE.onDidChangeZoomLevel; /** The zoom scale for an index, e.g. 1, 1.2, 1.4 */ -export function getZoomFactor(): number { - return WindowManager.INSTANCE.getZoomFactor(); +export function getZoomFactor(targetWindow: Window): number { + return WindowManager.INSTANCE.getZoomFactor(targetWindow); } -export function setZoomFactor(zoomFactor: number): void { - WindowManager.INSTANCE.setZoomFactor(zoomFactor); +export function setZoomFactor(zoomFactor: number, targetWindow: Window): void { + WindowManager.INSTANCE.setZoomFactor(zoomFactor, targetWindow); } export function setFullscreen(fullscreen: boolean, targetWindow: Window): void { diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index ea8a90ea6c0..85253acf938 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -87,7 +87,8 @@ export class MouseWheelClassifier { } public acceptStandardWheelEvent(e: StandardWheelEvent): void { - const osZoomFactor = dom.getWindow(e.browserEvent).devicePixelRatio / getZoomFactor(); + const targetWindow = dom.getWindow(e.browserEvent); + const osZoomFactor = targetWindow.devicePixelRatio / getZoomFactor(targetWindow); if (platform.isWindows || platform.isLinux) { // On Windows and Linux, the incoming delta events are multiplied with the OS zoom factor. // The OS zoom factor can be reverse engineered by using the device pixel ratio and the configured zoom factor into account. diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 641ee04aa63..6f02aa6fc22 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -516,6 +516,13 @@ export class CodeApplication extends Disposable { validatedIpcMain.on('vscode:reloadWindow', event => event.sender.reload()); + validatedIpcMain.handle('vscode:notifyZoomLevel', async (event, zoomLevel: number | undefined) => { + const window = this.windowsMainService?.getWindowById(event.sender.id); + if (window) { + window.notifyZoomLevel(zoomLevel); + } + }); + //#endregion } diff --git a/src/vs/code/electron-sandbox/issue/issueReporterService.ts b/src/vs/code/electron-sandbox/issue/issueReporterService.ts index d7f98f666b9..417b40f216f 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterService.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterService.ts @@ -127,7 +127,7 @@ export class IssueReporter extends Disposable { this.setUpTypes(); this.setEventHandlers(); - applyZoom(configuration.data.zoomLevel); + applyZoom(configuration.data.zoomLevel, mainWindow); this.applyStyles(configuration.data.styles); this.handleExtensionData(configuration.data.enabledExtensions); this.updateExperimentsInfo(configuration.data.experiments); @@ -435,12 +435,12 @@ export class IssueReporter extends Disposable { // Cmd/Ctrl + zooms in if (cmdOrCtrlKey && e.keyCode === 187) { - zoomIn(); + zoomIn(mainWindow); } // Cmd/Ctrl - zooms out if (cmdOrCtrlKey && e.keyCode === 189) { - zoomOut(); + zoomOut(mainWindow); } // With latest electron upgrade, cmd+a is no longer propagating correctly for inputs in this window on mac diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts index 1d2089e1586..91bb7854340 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts @@ -288,12 +288,12 @@ class ProcessExplorer { // Cmd/Ctrl + zooms in if (cmdOrCtrlKey && e.keyCode === 187) { - zoomIn(); + zoomIn(mainWindow); } // Cmd/Ctrl - zooms out if (cmdOrCtrlKey && e.keyCode === 189) { - zoomOut(); + zoomOut(mainWindow); } }; } @@ -595,7 +595,7 @@ export function startup(configuration: ProcessExplorerWindowConfiguration): void const platformClass = configuration.data.platform === 'win32' ? 'windows' : configuration.data.platform === 'linux' ? 'linux' : 'mac'; mainWindow.document.body.classList.add(platformClass); // used by our fonts createCodiconStyleSheet(); - applyZoom(configuration.data.zoomLevel); + applyZoom(configuration.data.zoomLevel, mainWindow); new ProcessExplorer(configuration.windowId, configuration.data); } diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index 38ad7ca52eb..25eb202ca4e 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -347,6 +347,7 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native colorScheme: IColorScheme; autoDetectHighContrast?: boolean; autoDetectColorScheme?: boolean; + isCustomZoomLevel?: boolean; perfMarks: PerformanceMark[]; diff --git a/src/vs/platform/window/electron-main/window.ts b/src/vs/platform/window/electron-main/window.ts index dbcef949cd8..6f744cf6454 100644 --- a/src/vs/platform/window/electron-main/window.ts +++ b/src/vs/platform/window/electron-main/window.ts @@ -81,6 +81,8 @@ export interface ICodeWindow extends IBaseWindow { updateTouchBar(items: ISerializableCommandAction[][]): void; + notifyZoomLevel(zoomLevel: number | undefined): void; + serializeWindowState(): IWindowState; } @@ -131,6 +133,7 @@ export interface IWindowState { x?: number; y?: number; mode?: WindowMode; + zoomLevel?: number; readonly display?: number; } diff --git a/src/vs/platform/window/electron-sandbox/window.ts b/src/vs/platform/window/electron-sandbox/window.ts index 55c34942677..d8a3301ff3c 100644 --- a/src/vs/platform/window/electron-sandbox/window.ts +++ b/src/vs/platform/window/electron-sandbox/window.ts @@ -4,21 +4,35 @@ *--------------------------------------------------------------------------------------------*/ import { getZoomLevel, setZoomFactor, setZoomLevel } from 'vs/base/browser/browser'; -import { getWindows } from 'vs/base/browser/dom'; +import { getActiveWindow, getWindows } from 'vs/base/browser/dom'; import { mainWindow } from 'vs/base/browser/window'; import { ISandboxGlobals, ipcRenderer, webFrame } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { zoomLevelToZoomFactor } from 'vs/platform/window/common/window'; +export enum ApplyZoomTarget { + ACTIVE_WINDOW = 1, + ALL_WINDOWS +} + /** * Apply a zoom level to the window. Also sets it in our in-memory * browser helper so that it can be accessed in non-electron layers. */ -export function applyZoom(zoomLevel: number): void { - for (const { window } of getWindows()) { - getGlobals(window)?.webFrame?.setZoomLevel(zoomLevel); +export function applyZoom(zoomLevel: number, target: ApplyZoomTarget | Window): void { + const targetWindows: Window[] = []; + if (target === ApplyZoomTarget.ACTIVE_WINDOW) { + targetWindows.push(getActiveWindow()); + } else if (target === ApplyZoomTarget.ALL_WINDOWS) { + targetWindows.push(...Array.from(getWindows()).map(({ window }) => window)); + } else { + targetWindows.push(target); + } + + for (const targetWindow of targetWindows) { + getGlobals(targetWindow)?.webFrame?.setZoomLevel(zoomLevel); + setZoomFactor(zoomLevelToZoomFactor(zoomLevel), targetWindow); + setZoomLevel(zoomLevel, targetWindow); } - setZoomFactor(zoomLevelToZoomFactor(zoomLevel)); - setZoomLevel(zoomLevel); } function getGlobals(win: Window): ISandboxGlobals | undefined { @@ -36,10 +50,10 @@ function getGlobals(win: Window): ISandboxGlobals | undefined { return undefined; } -export function zoomIn(): void { - applyZoom(getZoomLevel() + 1); +export function zoomIn(target: ApplyZoomTarget | Window): void { + applyZoom(getZoomLevel(typeof target === 'number' ? getActiveWindow() : target) + 1, target); } -export function zoomOut(): void { - applyZoom(getZoomLevel() - 1); +export function zoomOut(target: ApplyZoomTarget | Window): void { + applyZoom(getZoomLevel(typeof target === 'number' ? getActiveWindow() : target) - 1, target); } diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index 4972933c581..4f2bb8182de 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -478,6 +478,8 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { private currentHttpProxy: string | undefined = undefined; private currentNoProxy: string | undefined = undefined; + private customZoomLevel: number | undefined = undefined; + private readonly configObjectUrl = this._register(this.protocolMainService.createIPCObjectUrl()); private pendingLoadConfig: INativeWindowConfiguration | undefined; private wasLoaded = false; @@ -1050,6 +1052,11 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { configuration.fullscreen = this.isFullScreen; configuration.maximized = this._win.isMaximized(); configuration.partsSplash = this.themeMainService.getWindowSplash(); + configuration.zoomLevel = this.getZoomLevel(); + configuration.isCustomZoomLevel = typeof this.customZoomLevel === 'number'; + if (configuration.isCustomZoomLevel && configuration.partsSplash) { + configuration.partsSplash.zoomLevel = configuration.zoomLevel; + } // Update with latest perf marks mark('code/willOpenNewWindow'); @@ -1149,7 +1156,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { const defaultState = defaultWindowState(); - const res = { + return { mode: WindowMode.Fullscreen, display: display ? display.id : undefined, @@ -1161,10 +1168,9 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { width: this.windowState.width || defaultState.width, height: this.windowState.height || defaultState.height, x: this.windowState.x || 0, - y: this.windowState.y || 0 + y: this.windowState.y || 0, + zoomLevel: this.customZoomLevel }; - - return res; } const state: IWindowState = Object.create(null); @@ -1199,6 +1205,8 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { state.height = bounds.height; } + state.zoomLevel = this.customZoomLevel; + return state; } @@ -1207,6 +1215,11 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { let hasMultipleDisplays = false; if (state) { + + // Window zoom + this.customZoomLevel = state.zoomLevel; + + // Window dimensions try { const displays = screen.getAllDisplays(); hasMultipleDisplays = displays.length > 1; @@ -1443,6 +1456,19 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { } } + notifyZoomLevel(zoomLevel: number | undefined): void { + this.customZoomLevel = zoomLevel; + } + + private getZoomLevel(): number | undefined { + if (typeof this.customZoomLevel === 'number') { + return this.customZoomLevel; + } + + const windowSettings = this.configurationService.getValue('window'); + return windowSettings?.zoomLevel; + } + close(): void { this._win?.close(); } diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 1bfd4d3631f..a658f3fa5ba 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -132,7 +132,7 @@ export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowSt webPreferences: { enableWebSQL: false, spellcheck: false, - zoomFactor: zoomLevelToZoomFactor(windowSettings?.zoomLevel), + zoomFactor: zoomLevelToZoomFactor(windowState?.zoomLevel ?? windowSettings?.zoomLevel), autoplayPolicy: 'user-gesture-required', // Enable experimental css highlight api https://chromestatus.com/feature/5436441440026624 // Refs https://github.com/microsoft/vscode/issues/140098 diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 5a1473a0844..32019b8b42c 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -1456,7 +1456,6 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic isInitialStartup: options.initialStartup, perfMarks: getMarks(), os: { release: release(), hostname: hostname(), arch: arch() }, - zoomLevel: typeof windowConfig?.zoomLevel === 'number' ? windowConfig.zoomLevel : undefined, autoDetectHighContrast: windowConfig?.autoDetectHighContrast ?? true, autoDetectColorScheme: windowConfig?.autoDetectColorScheme ?? false, diff --git a/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts b/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts index d395dc44ab1..dbba00ae8f0 100644 --- a/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts +++ b/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts @@ -74,6 +74,7 @@ suite('WindowsFinder', () => { updateTouchBar(items: UriDto[][]): void { throw new Error('Method not implemented.'); } serializeWindowState(): IWindowState { throw new Error('Method not implemented'); } updateWindowControls(options: { height?: number | undefined; backgroundColor?: string | undefined; foregroundColor?: string | undefined }): void { throw new Error('Method not implemented.'); } + notifyZoomLevel(level: number): void { throw new Error('Method not implemented.'); } dispose(): void { } }; } diff --git a/src/vs/workbench/browser/parts/editor/editorParts.ts b/src/vs/workbench/browser/parts/editor/editorParts.ts index 7f36225bf2c..f99c1230bd6 100644 --- a/src/vs/workbench/browser/parts/editor/editorParts.ts +++ b/src/vs/workbench/browser/parts/editor/editorParts.ts @@ -20,6 +20,7 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IRectangle } from 'vs/platform/window/common/window'; import { getWindow } from 'vs/base/browser/dom'; +import { getZoomLevel } from 'vs/base/browser/browser'; interface IEditorPartsUIState { readonly auxiliary: IAuxiliaryEditorPartState[]; @@ -29,6 +30,7 @@ interface IEditorPartsUIState { interface IAuxiliaryEditorPartState { readonly state: IEditorPartUIState; readonly bounds?: IRectangle; + readonly zoomLevel?: number; } export class EditorParts extends MultiWindowParts implements IEditorGroupsService, IEditorPartsView { @@ -210,7 +212,8 @@ export class EditorParts extends MultiWindowParts implements IEditor for (const auxiliaryEditorPartState of uiState.auxiliary) { auxiliaryEditorPartPromises.push(this.createAuxiliaryEditorPart({ bounds: auxiliaryEditorPartState.bounds, - state: auxiliaryEditorPartState.state + state: auxiliaryEditorPartState.state, + zoomLevel: auxiliaryEditorPartState.zoomLevel })); } @@ -256,6 +259,14 @@ export class EditorParts extends MultiWindowParts implements IEditor }; } + return undefined; + })(), + zoomLevel: (() => { + const auxiliaryWindow = getWindow(part.getContainer()); + if (auxiliaryWindow) { + return getZoomLevel(auxiliaryWindow); + } + return undefined; })() }; diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 1f98cb50ef7..2e69d9da37c 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -170,7 +170,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { get minimumHeight(): number { const value = this.isCommandCenterVisible || (isWeb && isWCOEnabled()) ? 35 : 30; - return value / (this.useCounterZoom ? getZoomFactor() : 1); + return value / (this.useCounterZoom ? getZoomFactor(getWindow(this.element)) : 1); } get maximumHeight(): number { return this.minimumHeight; } @@ -674,7 +674,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { } protected onContextMenu(e: MouseEvent, menuId: MenuId): void { - const event = new StandardMouseEvent(getWindow(this.rootContainer), e); + const event = new StandardMouseEvent(getWindow(this.element), e); // Show it this.contextMenuService.showContextMenu({ @@ -719,7 +719,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { // 1. Shrinking below the window control size (zoom < 1) // 2. No custom items are present in the title bar - const zoomFactor = getZoomFactor(); + const zoomFactor = getZoomFactor(getWindow(this.element)); const noMenubar = this.currentMenubarVisibility === 'hidden' || this.currentMenubarVisibility === 'compact' || (!isWeb && isMacintosh); const noCommandCenter = !this.isCommandCenterVisible; @@ -738,7 +738,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { this.lastLayoutDimensions = dimension; if (getTitleBarStyle(this.configurationService) === 'custom') { - const zoomFactor = getZoomFactor(); + const zoomFactor = getZoomFactor(getWindow(this.element)); this.element.style.setProperty('--zoom-factor', zoomFactor.toString()); this.rootContainer.classList.toggle('counter-zoom', this.useCounterZoom); diff --git a/src/vs/workbench/electron-sandbox/actions/windowActions.ts b/src/vs/workbench/electron-sandbox/actions/windowActions.ts index 1e855db5d64..bee086ad56e 100644 --- a/src/vs/workbench/electron-sandbox/actions/windowActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/windowActions.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/actions'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; -import { applyZoom } from 'vs/platform/window/electron-sandbox/window'; +import { ApplyZoomTarget, applyZoom } from 'vs/platform/window/electron-sandbox/window'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { getZoomLevel } from 'vs/base/browser/browser'; import { FileKind } from 'vs/platform/files/common/files'; @@ -74,7 +74,7 @@ abstract class BaseZoomAction extends Action2 { super(desc); } - protected async setConfiguredZoomLevel(accessor: ServicesAccessor, level: number): Promise { + protected async setZoomLevel(accessor: ServicesAccessor, level: number, target: ApplyZoomTarget): Promise { const configurationService = accessor.get(IConfigurationService); level = Math.round(level); // when reaching smallest zoom, prevent fractional zoom levels @@ -83,21 +83,23 @@ abstract class BaseZoomAction extends Action2 { return; // https://github.com/microsoft/vscode/issues/48357 } - await configurationService.updateValue(BaseZoomAction.SETTING_KEY, level); + if (target === ApplyZoomTarget.ALL_WINDOWS) { + await configurationService.updateValue(BaseZoomAction.SETTING_KEY, level); + } - applyZoom(level); + applyZoom(level, target); } } -export class ZoomInAction extends BaseZoomAction { +export class ZoomInAllWindowsAction extends BaseZoomAction { constructor() { super({ id: 'workbench.action.zoomIn', title: { - value: localize('zoomIn', "Zoom In"), + value: localize('zoomIn', "Zoom In (All Windows)"), mnemonicTitle: localize({ key: 'miZoomIn', comment: ['&& denotes a mnemonic'] }, "&&Zoom In"), - original: 'Zoom In' + original: 'Zoom In (All Windows)' }, category: Categories.View, f1: true, @@ -115,19 +117,19 @@ export class ZoomInAction extends BaseZoomAction { } override run(accessor: ServicesAccessor): Promise { - return super.setConfiguredZoomLevel(accessor, getZoomLevel() + 1); + return super.setZoomLevel(accessor, getZoomLevel(getActiveWindow()) + 1, ApplyZoomTarget.ALL_WINDOWS); } } -export class ZoomOutAction extends BaseZoomAction { +export class ZoomOutAllWindowsAction extends BaseZoomAction { constructor() { super({ id: 'workbench.action.zoomOut', title: { - value: localize('zoomOut', "Zoom Out"), + value: localize('zoomOut', "Zoom Out (All Windows)"), mnemonicTitle: localize({ key: 'miZoomOut', comment: ['&& denotes a mnemonic'] }, "&&Zoom Out"), - original: 'Zoom Out' + original: 'Zoom Out (All Windows)' }, category: Categories.View, f1: true, @@ -149,19 +151,19 @@ export class ZoomOutAction extends BaseZoomAction { } override run(accessor: ServicesAccessor): Promise { - return super.setConfiguredZoomLevel(accessor, getZoomLevel() - 1); + return super.setZoomLevel(accessor, getZoomLevel(getActiveWindow()) - 1, ApplyZoomTarget.ALL_WINDOWS); } } -export class ZoomResetAction extends BaseZoomAction { +export class ZoomResetAllWindowsAction extends BaseZoomAction { constructor() { super({ id: 'workbench.action.zoomReset', title: { - value: localize('zoomReset', "Reset Zoom"), + value: localize('zoomReset', "Reset Zoom (All Windows)"), mnemonicTitle: localize({ key: 'miZoomReset', comment: ['&& denotes a mnemonic'] }, "&&Reset Zoom"), - original: 'Reset Zoom' + original: 'Reset Zoom (All Windows)' }, category: Categories.View, f1: true, @@ -178,7 +180,64 @@ export class ZoomResetAction extends BaseZoomAction { } override run(accessor: ServicesAccessor): Promise { - return super.setConfiguredZoomLevel(accessor, 0); + return super.setZoomLevel(accessor, 0, ApplyZoomTarget.ALL_WINDOWS); + } +} + +export class ZoomInActiveWindowAction extends BaseZoomAction { + + constructor() { + super({ + id: 'workbench.action.zoomInActiveWindow', + title: { + value: localize('zoomInActiveWindow', "Zoom In (Active Window)"), + original: 'Zoom In (Active Window)' + }, + category: Categories.View, + f1: true + }); + } + + override run(accessor: ServicesAccessor): Promise { + return super.setZoomLevel(accessor, getZoomLevel(getActiveWindow()) + 1, ApplyZoomTarget.ACTIVE_WINDOW); + } +} + +export class ZoomOutActiveWindowAction extends BaseZoomAction { + + constructor() { + super({ + id: 'workbench.action.zoomOutActiveWindow', + title: { + value: localize('zoomOutActiveWindow', "Zoom Out (Active Window)"), + original: 'Zoom Out (Active Window)' + }, + category: Categories.View, + f1: true + }); + } + + override run(accessor: ServicesAccessor): Promise { + return super.setZoomLevel(accessor, getZoomLevel(getActiveWindow()) - 1, ApplyZoomTarget.ACTIVE_WINDOW); + } +} + +export class ZoomResetActiveWindowAction extends BaseZoomAction { + + constructor() { + super({ + id: 'workbench.action.zoomResetActiveWindow', + title: { + value: localize('zoomResetActiveWindow', "Reset Zoom (Active Window)"), + original: 'Reset Zoom (Active Window)' + }, + category: Categories.View, + f1: true + }); + } + + override run(accessor: ServicesAccessor): Promise { + return super.setZoomLevel(accessor, 0, ApplyZoomTarget.ACTIVE_WINDOW); } } diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 843fbd70fbf..78dd92aa508 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -10,7 +10,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { ConfigureRuntimeArgumentsAction, ToggleDevToolsAction, ReloadWindowWithExtensionsDisabledAction, OpenUserDataFolderAction } from 'vs/workbench/electron-sandbox/actions/developerActions'; -import { ZoomResetAction, ZoomOutAction, ZoomInAction, CloseWindowAction, SwitchWindowAction, QuickSwitchWindowAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler } from 'vs/workbench/electron-sandbox/actions/windowActions'; +import { ZoomResetAllWindowsAction, ZoomOutAllWindowsAction, ZoomInAllWindowsAction, CloseWindowAction, SwitchWindowAction, QuickSwitchWindowAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler, ZoomInActiveWindowAction, ZoomOutActiveWindowAction, ZoomResetActiveWindowAction } from 'vs/workbench/electron-sandbox/actions/windowActions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -32,9 +32,13 @@ import { applicationConfigurationNodeBase, securityConfigurationNodeBase } from (function registerActions(): void { // Actions: Zoom - registerAction2(ZoomInAction); - registerAction2(ZoomOutAction); - registerAction2(ZoomResetAction); + registerAction2(ZoomInAllWindowsAction); + registerAction2(ZoomOutAllWindowsAction); + registerAction2(ZoomResetAllWindowsAction); + + registerAction2(ZoomInActiveWindowAction); + registerAction2(ZoomOutActiveWindowAction); + registerAction2(ZoomResetActiveWindowAction); // Actions: Window registerAction2(SwitchWindowAction); diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index 0acf57e3448..1118dd5d38f 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -116,8 +116,10 @@ export class DesktopMain extends Disposable { // and before the workbench is created to prevent flickering. // We also need to respect that zoom level can be configured per // workspace, so we need the resolved configuration service. + // Finally, it is possible for the window to have a custom + // zoom level that is not derived from settings. // (fixes https://github.com/microsoft/vscode/issues/187982) - this.applyConfiguredWindowZoomLevel(services.configurationService); + this.applyWindowZoomLevel(services.configurationService); // Create Workbench const workbench = new Workbench(mainWindow.document.body, { extraClasses: this.getExtraClasses() }, services.serviceCollection, services.logService); @@ -132,11 +134,16 @@ export class DesktopMain extends Disposable { this._register(instantiationService.createInstance(NativeWindow)); } - private applyConfiguredWindowZoomLevel(configurationService: IConfigurationService) { - const windowConfig = configurationService.getValue(); - const windowZoomLevel = typeof windowConfig.window?.zoomLevel === 'number' ? windowConfig.window.zoomLevel : 0; + private applyWindowZoomLevel(configurationService: IConfigurationService) { + let zoomLevel: number | undefined = undefined; + if (this.configuration.isCustomZoomLevel && typeof this.configuration.zoomLevel === 'number') { + zoomLevel = this.configuration.zoomLevel; + } else { + const windowConfig = configurationService.getValue(); + zoomLevel = typeof windowConfig.window?.zoomLevel === 'number' ? windowConfig.window.zoomLevel : 0; + } - applyZoom(windowZoomLevel); + applyZoom(zoomLevel, mainWindow); } private getExtraClasses(): string[] { diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts index 161395b56f6..e3854eefde7 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts @@ -38,7 +38,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { return super.minimumHeight; } - return (this.isCommandCenterVisible ? 35 : this.macTitlebarSize) / (this.useCounterZoom ? getZoomFactor() : 1); + return (this.isCommandCenterVisible ? 35 : this.macTitlebarSize) / (this.useCounterZoom ? getZoomFactor(getWindow(this.element)) : 1); } override get maximumHeight(): number { return this.minimumHeight; } @@ -201,7 +201,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { return; } - const zoomFactor = getZoomFactor(); + const zoomFactor = getZoomFactor(getWindow(this.element)); this.onContextMenu(new MouseEvent('mouseup', { clientX: x / zoomFactor, clientY: y / zoomFactor }), MenuId.TitleBarContext); })); } @@ -261,7 +261,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { // so that they can have the traffic lights rendered at the proper offset. // Ref https://github.com/microsoft/vscode/issues/159862 - const newHeight = (height > 0 || this.bigSurOrNewer) ? Math.round(height * getZoomFactor()) : this.macTitlebarSize; + const newHeight = (height > 0 || this.bigSurOrNewer) ? Math.round(height * getZoomFactor(getWindow(this.element))) : this.macTitlebarSize; if (newHeight !== this.cachedWindowControlHeight) { this.cachedWindowControlHeight = newHeight; this.nativeHostService.updateWindowControls({ diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index a0df8c476f2..9a52c01defa 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -7,17 +7,17 @@ import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { onUnexpectedError } from 'vs/base/common/errors'; import { equals } from 'vs/base/common/objects'; -import { EventType, EventHelper, addDisposableListener, ModifierKeyEmitter, getActiveElement, hasWindow, getWindow, getWindowById, getWindowId } from 'vs/base/browser/dom'; +import { EventType, EventHelper, addDisposableListener, ModifierKeyEmitter, getActiveElement, hasWindow, getWindow, getWindowById, getWindowId, getWindows } from 'vs/base/browser/dom'; import { Separator, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; import { EditorResourceAccessor, IUntitledTextResourceEditorInput, SideBySideEditor, pathsToEditors, IResourceDiffEditorInput, IUntypedEditorInput, IEditorPane, isResourceEditorInput, IResourceMergeEditorInput } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { WindowMinimumSize, IOpenFileRequest, IWindowsConfiguration, getTitleBarStyle, IAddFoldersRequest, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, INativeOpenFileRequest } from 'vs/platform/window/common/window'; +import { WindowMinimumSize, IOpenFileRequest, IWindowsConfiguration, getTitleBarStyle, IAddFoldersRequest, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, INativeOpenFileRequest, IWindowSettings } from 'vs/platform/window/common/window'; import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { applyZoom } from 'vs/platform/window/electron-sandbox/window'; -import { setFullscreen, getZoomLevel } from 'vs/base/browser/browser'; +import { ApplyZoomTarget, applyZoom } from 'vs/platform/window/electron-sandbox/window'; +import { setFullscreen, getZoomLevel, onDidChangeZoomLevel } from 'vs/base/browser/browser'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { ipcRenderer, process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; @@ -339,6 +339,22 @@ export class NativeWindow extends BaseWindow { } })); + this._register(onDidChangeZoomLevel(targetWindowId => { + if (targetWindowId !== mainWindow.vscodeWindowId) { + return; // only update our own window + } + + const configuredWindowZoomLevel = this.configurationService.getValue('window')?.zoomLevel; + const currentWindowZoomLevel = getZoomLevel(mainWindow); + + let notifyZoomLevel: number | undefined = undefined; + if (configuredWindowZoomLevel !== currentWindowZoomLevel) { + notifyZoomLevel = currentWindowZoomLevel; + } + + ipcRenderer.invoke('vscode:notifyZoomLevel', notifyZoomLevel); + })); + // Listen to visible editor changes (debounced in case a new editor opens immediately after) this._register(Event.debounce(this.editorService.onDidVisibleEditorsChange, () => undefined, 0, undefined, undefined, undefined, this._store)(() => this.maybeCloseWindow())); @@ -626,8 +642,16 @@ export class NativeWindow extends BaseWindow { const windowConfig = this.configurationService.getValue(); const windowZoomLevel = typeof windowConfig.window?.zoomLevel === 'number' ? windowConfig.window.zoomLevel : 0; - if (getZoomLevel() !== windowZoomLevel) { - applyZoom(windowZoomLevel); + let applyZoomLevel = false; + for (const { window } of getWindows()) { + if (getZoomLevel(window) !== windowZoomLevel) { + applyZoomLevel = true; + break; + } + } + + if (applyZoomLevel) { + applyZoom(windowZoomLevel, ApplyZoomTarget.ALL_WINDOWS); } } diff --git a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts index 5af8ccad75a..53803bb6316 100644 --- a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts @@ -32,6 +32,7 @@ export interface IAuxiliaryWindowOpenEvent { export interface IAuxiliaryWindowOpenOptions { readonly bounds?: Partial; + readonly zoomLevel?: number; } export interface IAuxiliaryWindowService { @@ -191,7 +192,7 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili ensureCodeWindow(targetWindow, resolvedWindowId); const containerDisposables = new DisposableStore(); - const { container, stylesLoaded } = this.createContainer(targetWindow, containerDisposables); + const { container, stylesLoaded } = this.createContainer(targetWindow, containerDisposables, options); const auxiliaryWindow = this.createAuxiliaryWindow(targetWindow, container, stylesLoaded); @@ -283,7 +284,7 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili return BrowserAuxiliaryWindowService.WINDOW_IDS++; } - protected createContainer(auxiliaryWindow: CodeWindow, disposables: DisposableStore): { stylesLoaded: Barrier; container: HTMLElement } { + protected createContainer(auxiliaryWindow: CodeWindow, disposables: DisposableStore, options?: IAuxiliaryWindowOpenOptions): { stylesLoaded: Barrier; container: HTMLElement } { this.patchMethods(auxiliaryWindow); this.applyMeta(auxiliaryWindow); diff --git a/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts index 3ac7684e596..04f04560893 100644 --- a/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts +++ b/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts @@ -5,7 +5,7 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { AuxiliaryWindow, BrowserAuxiliaryWindowService, IAuxiliaryWindowService } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService'; +import { AuxiliaryWindow, BrowserAuxiliaryWindowService, IAuxiliaryWindowOpenOptions, IAuxiliaryWindowService } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService'; import { ISandboxGlobals } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWindowsConfiguration } from 'vs/platform/window/common/window'; @@ -21,6 +21,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Barrier } from 'vs/base/common/async'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { applyZoom } from 'vs/platform/window/electron-sandbox/window'; type NativeCodeWindow = CodeWindow & { readonly vscode: ISandboxGlobals; @@ -81,12 +82,18 @@ export class NativeAuxiliaryWindowService extends BrowserAuxiliaryWindowService return windowId; } - protected override createContainer(auxiliaryWindow: NativeCodeWindow, disposables: DisposableStore) { + protected override createContainer(auxiliaryWindow: NativeCodeWindow, disposables: DisposableStore, options?: IAuxiliaryWindowOpenOptions) { // Zoom level - const windowConfig = this.configurationService.getValue(); - const windowZoomLevel = typeof windowConfig.window?.zoomLevel === 'number' ? windowConfig.window.zoomLevel : 0; - auxiliaryWindow.vscode.webFrame.setZoomLevel(windowZoomLevel); + let windowZoomLevel: number; + if (typeof options?.zoomLevel === 'number') { + windowZoomLevel = options.zoomLevel; + } else { + const windowConfig = this.configurationService.getValue(); + windowZoomLevel = typeof windowConfig.window?.zoomLevel === 'number' ? windowConfig.window.zoomLevel : 0; + } + + applyZoom(windowZoomLevel, auxiliaryWindow); return super.createContainer(auxiliaryWindow, disposables); } diff --git a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts index 49955b1c058..381c8be72ec 100644 --- a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts @@ -106,7 +106,7 @@ class NativeContextMenuService extends Disposable implements IContextMenuService let x: number | undefined; let y: number | undefined; - let zoom = getZoomFactor(); + let zoom = getZoomFactor(anchor instanceof HTMLElement ? dom.getWindow(anchor) : dom.getActiveWindow()); if (anchor instanceof HTMLElement) { const elementPosition = dom.getDomNodePagePosition(anchor); diff --git a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts index b3b906f09ed..f5e7db23472 100644 --- a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts +++ b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts @@ -27,6 +27,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity'; import { ILogService } from 'vs/platform/log/common/log'; import { IIssueDataProvider, IIssueUriRequestHandler, IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; +import { mainWindow } from 'vs/base/browser/window'; export class NativeIssueService implements IWorkbenchIssueService { declare readonly _serviceBrand: undefined; @@ -144,7 +145,7 @@ export class NativeIssueService implements IWorkbenchIssueService { const theme = this.themeService.getColorTheme(); const issueReporterData: IssueReporterData = Object.assign({ styles: getIssueReporterStyles(theme), - zoomLevel: getZoomLevel(), + zoomLevel: getZoomLevel(mainWindow), enabledExtensions: extensionData, experiments: experiments?.join('\n'), restrictedMode: !this.workspaceTrustManagementService.isWorkspaceTrusted(), @@ -158,7 +159,7 @@ export class NativeIssueService implements IWorkbenchIssueService { const theme = this.themeService.getColorTheme(); const data: ProcessExplorerData = { pid: this.environmentService.mainPid, - zoomLevel: getZoomLevel(), + zoomLevel: getZoomLevel(mainWindow), styles: { backgroundColor: getColor(theme, editorBackground), color: getColor(theme, editorForeground), From ea076f5bdff3496e36a1c18b73612a1c6487c8b0 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 25 Dec 2023 10:18:35 +0100 Subject: [PATCH 17/26] debt - tweak some keybindings --- .../browser/parts/editor/editorActions.ts | 6 ++++- .../contrib/files/browser/fileActions.ts | 1 - .../electron-sandbox/actions/windowActions.ts | 26 ++++++++++++++++--- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 351cff2e7b0..74f3f40069c 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -30,7 +30,7 @@ import { Action2, IAction2Options, MenuId } from 'vs/platform/actions/common/act import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IKeybindingRule, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ILogService } from 'vs/platform/log/common/log'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { ActiveEditorAvailableEditorIdsContext, ActiveEditorContext, ActiveEditorGroupEmptyContext, AuxiliaryBarVisibleContext, EditorPartMaximizedEditorGroupContext, EditorPartMultipleEditorGroupsContext, IsAuxiliaryWindowFocusedContext, MultipleEditorGroupsContext, SideBarVisibleContext } from 'vs/workbench/common/contextkeys'; @@ -2493,6 +2493,7 @@ abstract class BaseMoveCopyEditorToNewWindowAction extends Action2 { constructor( id: string, title: ICommandActionTitle, + keybinding: Omit | undefined, private readonly move: boolean ) { super({ @@ -2500,6 +2501,7 @@ abstract class BaseMoveCopyEditorToNewWindowAction extends Action2 { title, category: Categories.View, precondition: ActiveEditorContext, + keybinding, f1: true }); } @@ -2532,6 +2534,7 @@ export class MoveEditorToNewWindowAction extends BaseMoveCopyEditorToNewWindowAc mnemonicTitle: localize({ key: 'miMoveEditorToNewWindow', comment: ['&& denotes a mnemonic'] }, "&&Move Editor into New Window"), original: 'Move Editor into New Window' }, + undefined, true ); } @@ -2547,6 +2550,7 @@ export class CopyEditorToNewindowAction extends BaseMoveCopyEditorToNewWindowAct mnemonicTitle: localize({ key: 'miCopyEditorToNewWindow', comment: ['&& denotes a mnemonic'] }, "&&Copy Editor into New Window"), original: 'Copy Editor into New Window' }, + { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyO), weight: KeybindingWeight.WorkbenchContrib }, false ); } diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index da784179f5f..69959a9df8d 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -661,7 +661,6 @@ export class OpenActiveFileInEmptyWorkspace extends Action2 { title: { value: OpenActiveFileInEmptyWorkspace.LABEL, original: 'Open Active File in New Empty Workspace' }, f1: true, category: Categories.File, - keybinding: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyO), weight: KeybindingWeight.WorkbenchContrib }, precondition: EmptyWorkspaceSupportContext }); } diff --git a/src/vs/workbench/electron-sandbox/actions/windowActions.ts b/src/vs/workbench/electron-sandbox/actions/windowActions.ts index bee086ad56e..3d69577332d 100644 --- a/src/vs/workbench/electron-sandbox/actions/windowActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/windowActions.ts @@ -23,7 +23,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { Action2, IAction2Options, MenuId } from 'vs/platform/actions/common/actions'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { isMacintosh } from 'vs/base/common/platform'; import { getActiveWindow } from 'vs/base/browser/dom'; @@ -194,7 +194,12 @@ export class ZoomInActiveWindowAction extends BaseZoomAction { original: 'Zoom In (Active Window)' }, category: Categories.View, - f1: true + f1: true, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Equal), + secondary: [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Equal), KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.NumpadAdd)] + } }); } @@ -213,7 +218,16 @@ export class ZoomOutActiveWindowAction extends BaseZoomAction { original: 'Zoom Out (Active Window)' }, category: Categories.View, - f1: true + f1: true, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Minus), + secondary: [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Minus), KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.NumpadSubtract)], + linux: { + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Minus), + secondary: [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.NumpadSubtract)] + } + } }); } @@ -232,7 +246,11 @@ export class ZoomResetActiveWindowAction extends BaseZoomAction { original: 'Reset Zoom (Active Window)' }, category: Categories.View, - f1: true + f1: true, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Numpad0) + } }); } From 35c44eeb2790d76fb335974e1a0130d45e658808 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 25 Dec 2023 13:12:36 +0100 Subject: [PATCH 18/26] Aux window: reduce $window usage (#195888) --- src/vs/base/browser/browser.ts | 14 ++++----- src/vs/base/browser/dom.ts | 2 +- .../browser/standaloneThemeService.ts | 2 +- .../browser/inlineChatController.ts | 5 ++-- .../browser/inlineChatStrategies.ts | 29 +++++++++---------- .../view/cellParts/chat/cellChatController.ts | 4 +-- .../browser/browserHostColorSchemeService.ts | 4 +-- 7 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index d8bd1f089ce..5020e44a36b 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $window, CodeWindow } from 'vs/base/browser/window'; +import { $window, CodeWindow, mainWindow } from 'vs/base/browser/window'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, markAsSingleton } from 'vs/base/common/lifecycle'; @@ -158,9 +158,9 @@ class PixelRatioFacade { } } -export function addMatchMediaChangeListener(query: string | MediaQueryList, callback: (this: MediaQueryList, ev: MediaQueryListEvent) => any): void { +export function addMatchMediaChangeListener(targetWindow: Window, query: string | MediaQueryList, callback: (this: MediaQueryList, ev: MediaQueryListEvent) => any): void { if (typeof query === 'string') { - query = $window.matchMedia(query); + query = targetWindow.matchMedia(query); } query.addEventListener('change', callback); } @@ -210,11 +210,11 @@ export const isElectron = (userAgent.indexOf('Electron/') >= 0); export const isAndroid = (userAgent.indexOf('Android') >= 0); let standalone = false; -if ($window.matchMedia) { - const standaloneMatchMedia = $window.matchMedia('(display-mode: standalone) or (display-mode: window-controls-overlay)'); - const fullScreenMatchMedia = $window.matchMedia('(display-mode: fullscreen)'); +if (typeof mainWindow.matchMedia === 'function') { + const standaloneMatchMedia = mainWindow.matchMedia('(display-mode: standalone) or (display-mode: window-controls-overlay)'); + const fullScreenMatchMedia = mainWindow.matchMedia('(display-mode: fullscreen)'); standalone = standaloneMatchMedia.matches; - addMatchMediaChangeListener(standaloneMatchMedia, ({ matches }) => { + addMatchMediaChangeListener(mainWindow, standaloneMatchMedia, ({ matches }) => { // entering fullscreen would change standaloneMatchMedia.matches to false // if standalone is true (running as PWA) and entering fullscreen, skip this change if (standalone && fullScreenMatchMedia.matches) { diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 5379bd3814c..112ed1854d2 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -261,7 +261,7 @@ export let runAtThisOrScheduleAtNextAnimationFrame: (targetWindow: Window, runne */ export let scheduleAtNextAnimationFrame: (targetWindow: Window, runner: () => void, priority?: number) => IDisposable; -export function disposableWindowInterval(targetWindow: Window & typeof globalThis, handler: () => void | boolean /* stop interval */ | Promise, interval: number, iterations?: number): IDisposable { +export function disposableWindowInterval(targetWindow: Window, handler: () => void | boolean /* stop interval */ | Promise, interval: number, iterations?: number): IDisposable { let iteration = 0; const timer = targetWindow.setInterval(() => { iteration++; diff --git a/src/vs/editor/standalone/browser/standaloneThemeService.ts b/src/vs/editor/standalone/browser/standaloneThemeService.ts index 5fc765ed2d1..959ca90487a 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeService.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeService.ts @@ -261,7 +261,7 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe this._updateCSS(); })); - addMatchMediaChangeListener('(forced-colors: active)', () => { + addMatchMediaChangeListener(mainWindow, '(forced-colors: active)', () => { this._onOSSchemeChanged(); }); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 0916d9b82a9..31cfb0aa6a9 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { getWindow } from 'vs/base/browser/dom'; import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { Barrier, Queue, raceCancellation, raceCancellationError } from 'vs/base/common/async'; @@ -773,9 +774,9 @@ export class InlineChatController implements IEditorContribution { this._ignoreModelContentChanged = true; this._activeSession.wholeRange.trackEdits(editOperations); if (opts) { - await this._strategy.makeProgressiveChanges(editOperations, opts); + await this._strategy.makeProgressiveChanges(getWindow(this._editor.getContainerDomNode()), editOperations, opts); } else { - await this._strategy.makeChanges(editOperations); + await this._strategy.makeChanges(getWindow(this._editor.getContainerDomNode()), editOperations); } this._ctxDidEdit.set(this._activeSession.hasChangedText); } finally { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 83b5c32a982..56fe6fced08 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { disposableWindowInterval } from 'vs/base/browser/dom'; -import { $window } from 'vs/base/browser/window'; import { IAction, toAction } from 'vs/base/common/actions'; import { coalesceInPlace, equals, tail } from 'vs/base/common/arrays'; import { AsyncIterableObject, AsyncIterableSource } from 'vs/base/common/async'; @@ -77,9 +76,9 @@ export abstract class EditModeStrategy { abstract cancel(): Promise; - abstract makeProgressiveChanges(edits: ISingleEditOperation[], timings: ProgressingEditsOptions): Promise; + abstract makeProgressiveChanges(targetWindow: Window, edits: ISingleEditOperation[], timings: ProgressingEditsOptions): Promise; - abstract makeChanges(edits: ISingleEditOperation[]): Promise; + abstract makeChanges(targetWindow: Window, edits: ISingleEditOperation[]): Promise; abstract undoChanges(altVersionId: number): Promise; @@ -151,7 +150,7 @@ export class PreviewStrategy extends EditModeStrategy { // nothing to do } - override async makeChanges(_edits: ISingleEditOperation[]): Promise { + override async makeChanges(_targetWindow: Window, _edits: ISingleEditOperation[]): Promise { // nothing to do } @@ -244,7 +243,7 @@ export class LivePreviewStrategy extends EditModeStrategy { const targetAltVersion = textModelNSnapshotAltVersion ?? textModelNAltVersion; await undoModelUntil(modelN, targetAltVersion); } - override async makeChanges(edits: ISingleEditOperation[]): Promise { + override async makeChanges(_targetWindow: Window, edits: ISingleEditOperation[]): Promise { const cursorStateComputerAndInlineDiffCollection: ICursorStateComputer = (undoEdits) => { let last: Position | null = null; for (const edit of undoEdits) { @@ -266,7 +265,7 @@ export class LivePreviewStrategy extends EditModeStrategy { await this._updateDiffZones(); } - override async makeProgressiveChanges(edits: ISingleEditOperation[], opts: ProgressingEditsOptions): Promise { + override async makeProgressiveChanges(targetWindow: Window, edits: ISingleEditOperation[], opts: ProgressingEditsOptions): Promise { // push undo stop before first edit if (++this._editCount === 1) { @@ -285,7 +284,7 @@ export class LivePreviewStrategy extends EditModeStrategy { const wordCount = countWords(edit.text ?? ''); const speed = wordCount / durationInSec; // console.log({ durationInSec, wordCount, speed: wordCount / durationInSec }); - await performAsyncTextEdit(this._session.textModelN, asProgressiveEdit(edit, speed, opts.token)); + await performAsyncTextEdit(this._session.textModelN, asProgressiveEdit(targetWindow, edit, speed, opts.token)); } await renderTask; @@ -444,7 +443,7 @@ export function asAsyncEdit(edit: IIdentifiedSingleEditOperation): AsyncTextEdit } satisfies AsyncTextEdit; } -export function asProgressiveEdit(edit: IIdentifiedSingleEditOperation, wordsPerSec: number, token: CancellationToken): AsyncTextEdit { +export function asProgressiveEdit(targetWindow: Window, edit: IIdentifiedSingleEditOperation, wordsPerSec: number, token: CancellationToken): AsyncTextEdit { wordsPerSec = Math.max(10, wordsPerSec); @@ -452,7 +451,7 @@ export function asProgressiveEdit(edit: IIdentifiedSingleEditOperation, wordsPer let newText = edit.text ?? ''; // const wordCount = countWords(newText); - const handle = disposableWindowInterval($window, () => { + const handle = disposableWindowInterval(targetWindow, () => { const r = getNWords(newText, 1); stream.emitOne(r.value); @@ -584,15 +583,15 @@ export class LiveStrategy extends EditModeStrategy { await undoModelUntil(textModelN, altVersionId); } - override async makeChanges(edits: ISingleEditOperation[]): Promise { - return this._makeChanges(edits, undefined); + override async makeChanges(targetWindow: Window, edits: ISingleEditOperation[]): Promise { + return this._makeChanges(targetWindow, edits, undefined); } - override async makeProgressiveChanges(edits: ISingleEditOperation[], opts: ProgressingEditsOptions): Promise { - return this._makeChanges(edits, opts); + override async makeProgressiveChanges(targetWindow: Window, edits: ISingleEditOperation[], opts: ProgressingEditsOptions): Promise { + return this._makeChanges(targetWindow, edits, opts); } - private async _makeChanges(edits: ISingleEditOperation[], opts: ProgressingEditsOptions | undefined): Promise { + private async _makeChanges(targetWindow: Window, edits: ISingleEditOperation[], opts: ProgressingEditsOptions | undefined): Promise { // push undo stop before first edit if (++this._editCount === 1) { @@ -625,7 +624,7 @@ export class LiveStrategy extends EditModeStrategy { const wordCount = countWords(edit.text ?? ''); const speed = wordCount / durationInSec; // console.log({ durationInSec, wordCount, speed: wordCount / durationInSec }); - await performAsyncTextEdit(this._session.textModelN, asProgressiveEdit(edit, speed, opts.token), progress); + await performAsyncTextEdit(this._session.textModelN, asProgressiveEdit(targetWindow, edit, speed, opts.token), progress); } } else { diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts index 7dee82705f5..f02c0943fc7 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Dimension, h } from 'vs/base/browser/dom'; +import { Dimension, getWindow, h } from 'vs/base/browser/dom'; import { CancelablePromise, Queue, createCancelablePromise, raceCancellationError } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; @@ -468,7 +468,7 @@ class EditStrategy { const wordCount = countWords(edit.text ?? ''); const speed = wordCount / durationInSec; // console.log({ durationInSec, wordCount, speed: wordCount / durationInSec }); - await performAsyncTextEdit(editor.getModel(), asProgressiveEdit(edit, speed, opts.token)); + await performAsyncTextEdit(editor.getModel(), asProgressiveEdit(getWindow(editor.getContainerDomNode()), edit, speed, opts.token)); } } diff --git a/src/vs/workbench/services/themes/browser/browserHostColorSchemeService.ts b/src/vs/workbench/services/themes/browser/browserHostColorSchemeService.ts index ac32b57b136..ca31f33374f 100644 --- a/src/vs/workbench/services/themes/browser/browserHostColorSchemeService.ts +++ b/src/vs/workbench/services/themes/browser/browserHostColorSchemeService.ts @@ -25,10 +25,10 @@ export class BrowserHostColorSchemeService extends Disposable implements IHostCo private registerListeners(): void { - addMatchMediaChangeListener('(prefers-color-scheme: dark)', () => { + addMatchMediaChangeListener(mainWindow, '(prefers-color-scheme: dark)', () => { this._onDidSchemeChangeEvent.fire(); }); - addMatchMediaChangeListener('(forced-colors: active)', () => { + addMatchMediaChangeListener(mainWindow, '(forced-colors: active)', () => { this._onDidSchemeChangeEvent.fire(); }); } From ce283e0bc15bb106c42262cc45fa9c2702d58849 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 28 Dec 2023 06:31:09 +0100 Subject: [PATCH 19/26] editors - separate split in editor action --- .../parts/editor/editor.contribution.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 146e91e1b59..fe3238769b3 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -10,7 +10,8 @@ import { IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/ed import { TextCompareEditorActiveContext, ActiveEditorPinnedContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorAvailableEditorIdsContext, EditorPartMultipleEditorGroupsContext, ActiveEditorDirtyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, - EditorTabsVisibleContext, ActiveEditorLastInGroupContext, EditorPartMaximizedEditorGroupContext, MultipleEditorGroupsContext, InEditorZenModeContext, IsAuxiliaryEditorPartContext, ActiveCompareEditorOriginalWriteableContext + EditorTabsVisibleContext, ActiveEditorLastInGroupContext, EditorPartMaximizedEditorGroupContext, MultipleEditorGroupsContext, InEditorZenModeContext, + IsAuxiliaryEditorPartContext, ActiveCompareEditorOriginalWriteableContext } from 'vs/workbench/common/contextkeys'; import { SideBySideEditorInput, SideBySideEditorInputSerializer } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; @@ -41,13 +42,16 @@ import { ReOpenInTextEditorAction, DuplicateGroupDownAction, DuplicateGroupLeftAction, DuplicateGroupRightAction, DuplicateGroupUpAction, ToggleEditorTypeAction, SplitEditorToAboveGroupAction, SplitEditorToBelowGroupAction, SplitEditorToFirstGroupAction, SplitEditorToLastGroupAction, SplitEditorToLeftGroupAction, SplitEditorToNextGroupAction, SplitEditorToPreviousGroupAction, SplitEditorToRightGroupAction, NavigateForwardInEditsAction, NavigateBackwardsInEditsAction, NavigateForwardInNavigationsAction, NavigateBackwardsInNavigationsAction, NavigatePreviousInNavigationsAction, NavigatePreviousInEditsAction, NavigateToLastNavigationLocationAction, - MaximizeGroupHideSidebarAction, MoveEditorToNewWindowAction, CopyEditorToNewindowAction, RestoreEditorsToMainWindowAction, ToggleMaximizeEditorGroupAction, MinimizeOtherGroupsHideSidebarAction, CopyEditorGroupToNewWindowAction, MoveEditorGroupToNewWindowAction, NewEmptyEditorWindowAction + MaximizeGroupHideSidebarAction, MoveEditorToNewWindowAction, CopyEditorToNewindowAction, RestoreEditorsToMainWindowAction, ToggleMaximizeEditorGroupAction, MinimizeOtherGroupsHideSidebarAction, CopyEditorGroupToNewWindowAction, + MoveEditorGroupToNewWindowAction, NewEmptyEditorWindowAction } from 'vs/workbench/browser/parts/editor/editorActions'; import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_EDITOR_GROUP_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_PINNED_EDITOR_COMMAND_ID, CLOSE_SAVED_EDITORS_COMMAND_ID, GOTO_NEXT_CHANGE, GOTO_PREVIOUS_CHANGE, KEEP_EDITOR_COMMAND_ID, PIN_EDITOR_COMMAND_ID, SHOW_EDITORS_IN_GROUP, SPLIT_EDITOR_DOWN, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, TOGGLE_DIFF_SIDE_BY_SIDE, TOGGLE_KEEP_EDITORS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, setup as registerEditorCommands, REOPEN_WITH_COMMAND_ID, - TOGGLE_LOCK_GROUP_COMMAND_ID, UNLOCK_GROUP_COMMAND_ID, SPLIT_EDITOR_IN_GROUP, JOIN_EDITOR_IN_GROUP, FOCUS_FIRST_SIDE_EDITOR, FOCUS_SECOND_SIDE_EDITOR, TOGGLE_SPLIT_EDITOR_IN_GROUP_LAYOUT, LOCK_GROUP_COMMAND_ID, SPLIT_EDITOR, TOGGLE_MAXIMIZE_EDITOR_GROUP, MOVE_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, MOVE_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID, DIFF_SWAP_SIDES + TOGGLE_LOCK_GROUP_COMMAND_ID, UNLOCK_GROUP_COMMAND_ID, SPLIT_EDITOR_IN_GROUP, JOIN_EDITOR_IN_GROUP, FOCUS_FIRST_SIDE_EDITOR, FOCUS_SECOND_SIDE_EDITOR, TOGGLE_SPLIT_EDITOR_IN_GROUP_LAYOUT, LOCK_GROUP_COMMAND_ID, + SPLIT_EDITOR, TOGGLE_MAXIMIZE_EDITOR_GROUP, MOVE_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, MOVE_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, + NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID, DIFF_SWAP_SIDES } from 'vs/workbench/browser/parts/editor/editorCommands'; import { inQuickPickContext, getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -394,10 +398,10 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: SPLIT_ED MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: SPLIT_EDITOR_DOWN, title: localize('splitDown', "Split Down") }, group: '5_split', order: 20 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: SPLIT_EDITOR_LEFT, title: localize('splitLeft', "Split Left") }, group: '5_split', order: 30 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: SPLIT_EDITOR_RIGHT, title: localize('splitRight', "Split Right") }, group: '5_split', order: 40 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: SPLIT_EDITOR_IN_GROUP, title: localize('splitInGroup', "Split in Group") }, group: '5_split', order: 50, when: ActiveEditorCanSplitInGroupContext }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: JOIN_EDITOR_IN_GROUP, title: localize('joinInGroup', "Join in Group") }, group: '5_split', order: 50, when: SideBySideEditorActiveContext }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: MOVE_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, title: localize('moveToNewWindow', "Move into New Window") }, group: '6_new_window', order: 10 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: COPY_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, title: localize('copyToNewWindow', "Copy into New Window") }, group: '6_new_window', order: 20 }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: SPLIT_EDITOR_IN_GROUP, title: localize('splitInGroup', "Split in Group") }, group: '6_split_in_group', order: 10, when: ActiveEditorCanSplitInGroupContext }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: JOIN_EDITOR_IN_GROUP, title: localize('joinInGroup', "Join in Group") }, group: '6_split_in_group', order: 10, when: SideBySideEditorActiveContext }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: MOVE_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, title: localize('moveToNewWindow', "Move into New Window") }, group: '7_new_window', order: 10 }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: COPY_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, title: localize('copyToNewWindow', "Copy into New Window") }, group: '7_new_window', order: 20 }); // Editor Title Menu MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_DIFF_SIDE_BY_SIDE, title: localize('inlineView', "Inline View"), toggled: ContextKeyExpr.equals('config.diffEditor.renderSideBySide', false) }, group: '1_diff', order: 10, when: ContextKeyExpr.has('isInDiffEditor') }); From 83b909c39f0ce5368d3a41a30c609de86a2e106e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 28 Dec 2023 08:04:55 +0100 Subject: [PATCH 20/26] aux window - ensure focus window before showing editor close confirmation --- src/vs/workbench/browser/parts/editor/editorGroupView.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index bb23015b994..a51faaefc23 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -55,6 +55,7 @@ import { EditorGroupWatermark } from 'vs/workbench/browser/parts/editor/editorGr import { EditorTitleControl } from 'vs/workbench/browser/parts/editor/editorTitleControl'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export class EditorGroupView extends Themable implements IEditorGroupView { @@ -154,7 +155,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @ILogService private readonly logService: ILogService, - @IEditorResolverService private readonly editorResolverService: IEditorResolverService + @IEditorResolverService private readonly editorResolverService: IEditorResolverService, + @IHostService private readonly hostService: IHostService ) { super(themeService); @@ -1605,6 +1607,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { await this.doOpenEditor(editor); } + // Ensure our window has focus since we are about to show a dialog + await this.hostService.focus(getWindow(this.element)); + // Let editor handle confirmation if implemented if (typeof editor.closeHandler?.confirm === 'function') { confirmation = await editor.closeHandler.confirm([{ editor, groupId: this.id }]); From df8db3a75a49b85c9636530a3557cdbc639f7bdc Mon Sep 17 00:00:00 2001 From: Mahmoud Salah Date: Sun, 31 Dec 2023 08:28:35 +0100 Subject: [PATCH 21/26] =?UTF-8?q?For=20open=20diff=20editors,=20resolve=20?= =?UTF-8?q?the=20underlying=20original=20editor=20to=20set=20=E2=80=A6=20(?= =?UTF-8?q?#201597)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * For open diff editors, resolve the underlying original editor to set the appropriate language debugger. * Get modified editor rather than original. --------- Co-authored-by: Mahmoud Khalil --- .../workbench/contrib/debug/browser/welcomeView.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/welcomeView.ts b/src/vs/workbench/contrib/debug/browser/welcomeView.ts index cdff715b9bf..b0c09a24c52 100644 --- a/src/vs/workbench/contrib/debug/browser/welcomeView.ts +++ b/src/vs/workbench/contrib/debug/browser/welcomeView.ts @@ -20,7 +20,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { WorkbenchStateContext } from 'vs/workbench/common/contextkeys'; import { OpenFolderAction, OpenFileAction, OpenFileFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { isMacintosh, isWeb } from 'vs/base/common/platform'; -import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -62,7 +62,11 @@ export class WelcomeView extends ViewPane { this.debugStartLanguageContext.set(lastSetLanguage); const setContextKey = () => { - const editorControl = this.editorService.activeTextEditorControl; + let editorControl = this.editorService.activeTextEditorControl; + if (isDiffEditor(editorControl)) { + editorControl = editorControl.getModifiedEditor(); + } + if (isCodeEditor(editorControl)) { const model = editorControl.getModel(); const language = model ? model.getLanguageId() : undefined; @@ -82,7 +86,11 @@ export class WelcomeView extends ViewPane { this._register(editorService.onDidActiveEditorChange(() => { disposables.clear(); - const editorControl = this.editorService.activeTextEditorControl; + let editorControl = this.editorService.activeTextEditorControl; + if (isDiffEditor(editorControl)) { + editorControl = editorControl.getModifiedEditor(); + } + if (isCodeEditor(editorControl)) { disposables.add(editorControl.onDidChangeModelLanguage(setContextKey)); } From 12656dc79dc91f1e782e1b85b549fc85cb7886c2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 3 Jan 2024 08:40:05 +0100 Subject: [PATCH 22/26] aux window - fix issue with restoring when main window is empty (#201698) --- .../workbench/browser/parts/editor/editorPart.ts | 14 ++++++-------- .../workbench/browser/parts/editor/editorParts.ts | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 8aac8aa2023..be83c793d58 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -251,8 +251,8 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { return !!this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY]; } - private _didRestoreState = false; - get didRestoreState(): boolean { return this._didRestoreState; } + private _willRestoreState = false; + get willRestoreState(): boolean { return this._willRestoreState; } getGroups(order = GroupsOrder.CREATION_TIME): IEditorGroupView[] { switch (order) { @@ -986,7 +986,8 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { )); // Grid control - this.doCreateGridControl(options); + this._willRestoreState = !options || options.restorePreviousState; + this.doCreateGridControl(); // Centered layout widget this.centeredLayoutWidget = this._register(new CenteredViewLayout(this.container, this.gridWidgetView, this.profileMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY], this._partOptions.centeredLayoutFixedWidth)); @@ -1145,11 +1146,11 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { return false; } - private doCreateGridControl(options?: IEditorPartCreationOptions): void { + private doCreateGridControl(): void { // Grid Widget (with previous UI state) let restoreError = false; - if (!options || options.restorePreviousState) { + if (this._willRestoreState) { restoreError = !this.doCreateGridControlWithPreviousState(); } @@ -1179,9 +1180,6 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { // Grid Widget this.doCreateGridControlWithState(uiState.serializedGrid, uiState.activeGroup); - - // Remember that we did restore previous state - this._didRestoreState = true; } catch (error) { // Log error diff --git a/src/vs/workbench/browser/parts/editor/editorParts.ts b/src/vs/workbench/browser/parts/editor/editorParts.ts index f99c1230bd6..5ec469b889d 100644 --- a/src/vs/workbench/browser/parts/editor/editorParts.ts +++ b/src/vs/workbench/browser/parts/editor/editorParts.ts @@ -203,7 +203,7 @@ export class EditorParts extends MultiWindowParts implements IEditor // when the main part did restore. It is possible // that restoring was not attempted because specific // editors were opened. - if (this.mainPart.didRestoreState) { + if (this.mainPart.willRestoreState) { const uiState: IEditorPartsUIState | undefined = this.workspaceMemento[EditorParts.EDITOR_PARTS_UI_STATE_STORAGE_KEY]; if (uiState?.auxiliary.length) { const auxiliaryEditorPartPromises: Promise[] = []; From 93a6a2a12574e98ecaf5a47e991a8c0db660462a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 3 Jan 2024 12:25:14 +0100 Subject: [PATCH 23/26] untitled - encapsulate and update model within input (#201712) --- .../parts/editor/textResourceEditor.ts | 4 +- .../test/browser/textEditorService.test.ts | 6 +-- .../common/untitledTextEditorHandler.ts | 4 +- .../common/untitledTextEditorInput.ts | 30 ++++++++++--- .../common/untitledTextEditorService.ts | 11 +++++ .../test/browser/untitledTextEditor.test.ts | 43 +++++++++++++------ 6 files changed, 72 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 2da5fb20fa3..34762256f40 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -156,7 +156,7 @@ export class TextResourceEditor extends AbstractTextResourceEditor { } private onDidEditorPaste(e: IPasteEvent, codeEditor: ICodeEditor): void { - if (this.input instanceof UntitledTextEditorInput && this.input.model.hasLanguageSetExplicitly) { + if (this.input instanceof UntitledTextEditorInput && this.input.hasLanguageSetExplicitly) { return; // do not override language if it was set explicitly } @@ -205,7 +205,7 @@ export class TextResourceEditor extends AbstractTextResourceEditor { if (candidateLanguage && candidateLanguage.id !== PLAINTEXT_LANGUAGE_ID) { if (this.input instanceof UntitledTextEditorInput && candidateLanguage.source === 'event') { // High confidence, set language id at TextEditorModel level to block future auto-detection - this.input.model.setLanguageId(candidateLanguage.id); + this.input.setLanguageId(candidateLanguage.id); } else { textModel.setLanguage(this.languageService.createById(candidateLanguage.id)); } diff --git a/src/vs/workbench/services/textfile/test/browser/textEditorService.test.ts b/src/vs/workbench/services/textfile/test/browser/textEditorService.test.ts index 4e1ea75097f..749f3cc6dc5 100644 --- a/src/vs/workbench/services/textfile/test/browser/textEditorService.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textEditorService.test.ts @@ -129,14 +129,14 @@ suite('TextEditorService', () => { // Untyped Input (untitled with file path) input = disposables.add(service.createTextEditor({ resource: URI.file('/some/path.txt'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } })); assert(input instanceof UntitledTextEditorInput); - assert.ok((input as UntitledTextEditorInput).model.hasAssociatedFilePath); + assert.ok((input as UntitledTextEditorInput).hasAssociatedFilePath); // Untyped Input (untitled with untitled resource) untypedInput = { resource: URI.parse('untitled://Untitled-1'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }; assert.ok(isUntitledResourceEditorInput(untypedInput)); input = disposables.add(service.createTextEditor(untypedInput)); assert(input instanceof UntitledTextEditorInput); - assert.ok(!(input as UntitledTextEditorInput).model.hasAssociatedFilePath); + assert.ok(!(input as UntitledTextEditorInput).hasAssociatedFilePath); // Untyped input (untitled with custom resource, but forceUntitled) untypedInput = { resource: URI.file('/fake'), forceUntitled: true }; @@ -149,7 +149,7 @@ suite('TextEditorService', () => { input = disposables.add(service.createTextEditor({ resource: URI.parse('untitled-custom://some/path'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } })); assert(input instanceof UntitledTextEditorInput); - assert.ok((input as UntitledTextEditorInput).model.hasAssociatedFilePath); + assert.ok((input as UntitledTextEditorInput).hasAssociatedFilePath); provider.dispose(); diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts index 919261dd687..3674546202b 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts @@ -47,7 +47,7 @@ export class UntitledTextEditorInputSerializer implements IEditorSerializer { const untitledTextEditorInput = editorInput as UntitledTextEditorInput; let resource = untitledTextEditorInput.resource; - if (untitledTextEditorInput.model.hasAssociatedFilePath) { + if (untitledTextEditorInput.hasAssociatedFilePath) { resource = toLocalResource(resource, this.environmentService.remoteAuthority, this.pathService.defaultUriScheme); // untitled with associated file path use the local schema } @@ -59,7 +59,7 @@ export class UntitledTextEditorInputSerializer implements IEditorSerializer { const languageIdCandidate = untitledTextEditorInput.getLanguageId(); if (languageIdCandidate !== PLAINTEXT_LANGUAGE_ID) { languageId = languageIdCandidate; - } else if (untitledTextEditorInput.model.hasLanguageSetExplicitly) { + } else if (untitledTextEditorInput.hasLanguageSetExplicitly) { languageId = languageIdCandidate; } diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts index 766081132c1..1d67b04088d 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts @@ -18,7 +18,7 @@ import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { dispose, IReference } from 'vs/base/common/lifecycle'; +import { DisposableStore, dispose, IReference } from 'vs/base/common/lifecycle'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; /** @@ -37,10 +37,11 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp } private modelResolve: Promise | undefined = undefined; + private readonly modelDisposables = this._register(new DisposableStore()); private cachedUntitledTextEditorModelReference: IReference | undefined = undefined; constructor( - readonly model: IUntitledTextEditorModel, + protected model: IUntitledTextEditorModel, @ITextFileService textFileService: ITextFileService, @ILabelService labelService: ILabelService, @IEditorService editorService: IEditorService, @@ -54,16 +55,31 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp super(model.resource, undefined, editorService, textFileService, labelService, fileService, filesConfigurationService, textResourceConfigurationService); this.registerModelListeners(model); + + this._register(this.textFileService.untitled.onDidCreate(model => this.onDidCreateUntitledModel(model))); } private registerModelListeners(model: IUntitledTextEditorModel): void { + this.modelDisposables.clear(); // re-emit some events from the model - this._register(model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); - this._register(model.onDidChangeName(() => this._onDidChangeLabel.fire())); + this.modelDisposables.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); + this.modelDisposables.add(model.onDidChangeName(() => this._onDidChangeLabel.fire())); // a reverted untitled text editor model renders this input disposed - this._register(model.onDidRevert(() => this.dispose())); + this.modelDisposables.add(model.onDidRevert(() => this.dispose())); + } + + private onDidCreateUntitledModel(model: IUntitledTextEditorModel): void { + if (isEqual(model.resource, this.model.resource) && model !== this.model) { + + // Ensure that we keep our model up to date with + // the actual model from the service so that we + // never get out of sync with the truth. + + this.model = model; + this.registerModelListeners(model); + } } override getName(): string { @@ -116,6 +132,10 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp return this.model.setEncoding(encoding); } + get hasLanguageSetExplicitly() { return this.model.hasLanguageSetExplicitly; } + + get hasAssociatedFilePath() { return this.model.hasAssociatedFilePath; } + setLanguageId(languageId: string, source?: string): void { this.model.setLanguageId(languageId, source); } diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts index 8b4d6a5919e..dc465ef62fb 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts @@ -78,6 +78,11 @@ export interface IUntitledTextEditorModelManager { */ readonly onDidChangeLabel: Event; + /** + * Events for when untitled text editor models are created. + */ + readonly onDidCreate: Event; + /** * Events for when untitled text editors are about to be disposed. */ @@ -143,6 +148,9 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe private readonly _onDidChangeEncoding = this._register(new Emitter()); readonly onDidChangeEncoding = this._onDidChangeEncoding.event; + private readonly _onDidCreate = this._register(new Emitter()); + readonly onDidCreate = this._onDidCreate.event; + private readonly _onWillDispose = this._register(new Emitter()); readonly onWillDispose = this._onWillDispose.event; @@ -267,6 +275,9 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe // Add to cache this.mapResourceToModel.set(model.resource, model); + // Emit as event + this._onDidCreate.fire(model); + // If the model is dirty right from the beginning, // make sure to emit this as an event if (model.isDirty()) { diff --git a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts index f65c4f2c15d..133ee3f3298 100644 --- a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts +++ b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts @@ -26,6 +26,10 @@ import { timeout } from 'vs/base/common/async'; suite('Untitled text editors', () => { + class TestUntitledTextEditorInput extends UntitledTextEditorInput { + getModel() { return this.model; } + } + const disposables = new DisposableStore(); let instantiationService: IInstantiationService; let accessor: TestServiceAccessor; @@ -44,11 +48,19 @@ suite('Untitled text editors', () => { const service = accessor.untitledTextEditorService; const workingCopyService = accessor.workingCopyService; - const input1 = instantiationService.createInstance(UntitledTextEditorInput, service.create()); + const events: IUntitledTextEditorModel[] = []; + disposables.add(service.onDidCreate(model => { + events.push(model); + })); + + const input1 = instantiationService.createInstance(TestUntitledTextEditorInput, service.create()); await input1.resolve(); - assert.strictEqual(service.get(input1.resource), input1.model); + assert.strictEqual(service.get(input1.resource), input1.getModel()); assert.ok(!accessor.untitledTextEditorService.isUntitledWithAssociatedResource(input1.resource)); + assert.strictEqual(events.length, 1); + assert.strictEqual(events[0].resource.toString(), input1.getModel().resource.toString()); + assert.ok(service.get(input1.resource)); assert.ok(!service.get(URI.file('testing'))); @@ -59,16 +71,16 @@ suite('Untitled text editors', () => { assert.ok(!input1.hasCapability(EditorInputCapabilities.RequiresTrust)); assert.ok(!input1.hasCapability(EditorInputCapabilities.Scratchpad)); - const input2 = instantiationService.createInstance(UntitledTextEditorInput, service.create()); - assert.strictEqual(service.get(input2.resource), input2.model); + const input2 = instantiationService.createInstance(TestUntitledTextEditorInput, service.create()); + assert.strictEqual(service.get(input2.resource), input2.getModel()); // toUntyped() const untypedInput = input1.toUntyped({ preserveViewState: 0 }); assert.strictEqual(untypedInput.forceUntitled, true); // get() - assert.strictEqual(service.get(input1.resource), input1.model); - assert.strictEqual(service.get(input2.resource), input2.model); + assert.strictEqual(service.get(input1.resource), input1.getModel()); + assert.strictEqual(service.get(input2.resource), input2.getModel()); // revert() await input1.revert(0); @@ -80,6 +92,9 @@ suite('Untitled text editors', () => { assert.strictEqual(await service.resolve({ untitledResource: input2.resource }), model); assert.ok(service.get(model.resource)); + assert.strictEqual(events.length, 2); + assert.strictEqual(events[1].resource.toString(), input2.resource.toString()); + assert.ok(!input2.isDirty()); const resourcePromise = awaitDidChangeDirty(accessor.untitledTextEditorService); @@ -214,10 +229,10 @@ suite('Untitled text editors', () => { const service = accessor.untitledTextEditorService; const workingCopyService = accessor.workingCopyService; - const untitled = disposables.add(instantiationService.createInstance(UntitledTextEditorInput, service.create({ initialValue: 'Hello World' }))); + const untitled = disposables.add(instantiationService.createInstance(TestUntitledTextEditorInput, service.create({ initialValue: 'Hello World' }))); assert.ok(untitled.isDirty()); - const backup = (await untitled.model.backup(CancellationToken.None)).content; + const backup = (await untitled.getModel().backup(CancellationToken.None)).content; if (isReadableStream(backup)) { const value = await streamToBuffer(backup as VSBufferReadableStream); assert.strictEqual(value.toString(), 'Hello World'); @@ -307,9 +322,9 @@ suite('Untitled text editors', () => { const model = disposables.add(service.create()); const input = disposables.add(instantiationService.createInstance(UntitledTextEditorInput, model)); - assert.ok(!input.model.hasLanguageSetExplicitly); + assert.ok(!input.hasLanguageSetExplicitly); input.setLanguageId(PLAINTEXT_LANGUAGE_ID); - assert.ok(input.model.hasLanguageSetExplicitly); + assert.ok(input.hasLanguageSetExplicitly); assert.strictEqual(input.getLanguageId(), PLAINTEXT_LANGUAGE_ID); }); @@ -327,9 +342,9 @@ suite('Untitled text editors', () => { const input = disposables.add(instantiationService.createInstance(UntitledTextEditorInput, model)); disposables.add(await input.resolve()); - assert.ok(!input.model.hasLanguageSetExplicitly); + assert.ok(!input.hasLanguageSetExplicitly); model.textEditorModel!.setLanguage(accessor.languageService.createById(language)); - assert.ok(input.model.hasLanguageSetExplicitly); + assert.ok(input.hasLanguageSetExplicitly); assert.strictEqual(model.getLanguageId(), language); }); @@ -346,12 +361,12 @@ suite('Untitled text editors', () => { const input = disposables.add(instantiationService.createInstance(UntitledTextEditorInput, model)); await input.resolve(); - assert.ok(!input.model.hasLanguageSetExplicitly); + assert.ok(!input.hasLanguageSetExplicitly); model.textEditorModel!.setLanguage( accessor.languageService.createById(language), // This is really what this is testing LanguageDetectionLanguageEventSource); - assert.ok(!input.model.hasLanguageSetExplicitly); + assert.ok(!input.hasLanguageSetExplicitly); assert.strictEqual(model.getLanguageId(), language); }); From 17e0dec8cf3a60d75e5517093752af6f8e8e1978 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 3 Jan 2024 12:29:18 +0100 Subject: [PATCH 24/26] Fixes #198471 --- .../diffEditorViewZones.ts | 23 ++++++++++++++++--- .../common/services/editorSimpleWorker.ts | 3 ++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts index 480280da8f1..ac5b8886ac3 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts @@ -159,10 +159,17 @@ export class DiffEditorViewZones extends Disposable { const deletedCodeLineBreaksComputer = !renderSideBySide ? this._editors.modified._getViewModel()?.createLineBreaksComputer() : undefined; if (deletedCodeLineBreaksComputer) { + const originalModel = this._editors.original.getModel()!; for (const a of alignmentsVal) { if (a.diff) { for (let i = a.originalRange.startLineNumber; i < a.originalRange.endLineNumberExclusive; i++) { - deletedCodeLineBreaksComputer?.addRequest(this._editors.original.getModel()!.getLineContent(i), null, null); + // `i` can be out of bound when the diff has not been updated yet. + // In this case, we do an early return. + // TODO@hediet: Fix this by applying the edit directly to the diff model, so that the diff is always valid. + if (i > originalModel.getLineCount()) { + return { orig: origViewZones, mod: modViewZones }; + } + deletedCodeLineBreaksComputer?.addRequest(originalModel.getLineContent(i), null, null); } } } @@ -186,8 +193,15 @@ export class DiffEditorViewZones extends Disposable { const deletedCodeDomNode = document.createElement('div'); deletedCodeDomNode.classList.add('view-lines', 'line-delete', 'monaco-mouse-cursor-text'); + const originalModel = this._editors.original.getModel()!; + // `a.originalRange` can be out of bound when the diff has not been updated yet. + // In this case, we do an early return. + // TODO@hediet: Fix this by applying the edit directly to the diff model, so that the diff is always valid. + if (a.originalRange.endLineNumberExclusive - 1 > originalModel.getLineCount()) { + return { orig: origViewZones, mod: modViewZones }; + } const source = new LineSource( - a.originalRange.mapToLineArray(l => this._editors.original.getModel()!.tokenization.getLineTokens(l)), + a.originalRange.mapToLineArray(l => originalModel.tokenization.getLineTokens(l)), a.originalRange.mapToLineArray(_ => lineBreakData[lineBreakDataIdx++]), mightContainNonBasicASCII, mightContainRTL, @@ -551,7 +565,10 @@ function computeRangeAlignment( // There is some unmodified text on this line before the diff emitAlignment(i.originalRange.startLineNumber, i.modifiedRange.startLineNumber); } - if (i.originalRange.endColumn < originalEditor.getModel()!.getLineMaxColumn(i.originalRange.endLineNumber)) { + const originalModel = originalEditor.getModel()!; + // When the diff is invalid, the ranges might be out of bounds (this should be fixed in the diff model by applying edits directly). + const maxColumn = i.originalRange.endLineNumber <= originalModel.getLineCount() ? originalModel.getLineMaxColumn(i.originalRange.endLineNumber) : Number.MAX_SAFE_INTEGER; + if (i.originalRange.endColumn < maxColumn) { // // There is some unmodified text on this line after the diff emitAlignment(i.originalRange.endLineNumber, i.modifiedRange.endLineNumber); } diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index 12b28f16713..f03e018cac0 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -410,7 +410,8 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { return null; } - return EditorSimpleWorker.computeDiff(original, modified, options, algorithm); + const result = EditorSimpleWorker.computeDiff(original, modified, options, algorithm); + return result; } private static computeDiff(originalTextModel: ICommonModel | ITextModel, modifiedTextModel: ICommonModel | ITextModel, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): IDiffComputationResult { From b69fb3a61bf0c51cb3210f1da71c0f4d3d0e509b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 3 Jan 2024 20:26:57 +0530 Subject: [PATCH 25/26] fix #201707 (#201722) --- .../contrib/extensions/browser/extensionsWorkbenchService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 92097d03ccc..34e22edab47 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -1102,7 +1102,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (isUninstalled) { const canRemoveRunningExtension = runningExtension && this.extensionService.canRemoveExtension(runningExtension); const isSameExtensionRunning = runningExtension && (!extension.server || extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension))); - if (!canRemoveRunningExtension && isSameExtensionRunning) { + if (!canRemoveRunningExtension && isSameExtensionRunning && !runningExtension.isUnderDevelopment) { return nls.localize('postUninstallTooltip', "Please reload Visual Studio Code to complete the uninstallation of this extension."); } return undefined; From a0b228b51c9c7f8224dd8873927016cfb6797a80 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Jan 2024 17:41:14 +0100 Subject: [PATCH 26/26] Bump actions/setup-node from 3 to 4 (#196967) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 4. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-node dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/basic.yml | 6 +++--- .github/workflows/ci.yml | 8 ++++---- .github/workflows/monaco-editor.yml | 2 +- .github/workflows/telemetry.yml | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml index 3fd6daf0048..92c75ebdd9a 100644 --- a/.github/workflows/basic.yml +++ b/.github/workflows/basic.yml @@ -29,7 +29,7 @@ jobs: sudo update-rc.d xvfb defaults sudo service xvfb start - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version-file: .nvmrc @@ -81,7 +81,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version-file: .nvmrc @@ -143,7 +143,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version-file: .nvmrc diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a598efa9f37..b75e45b49c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version-file: .nvmrc @@ -113,7 +113,7 @@ jobs: sudo update-rc.d xvfb defaults sudo service xvfb start - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version-file: .nvmrc @@ -184,7 +184,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version-file: .nvmrc @@ -256,7 +256,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version-file: .nvmrc diff --git a/.github/workflows/monaco-editor.yml b/.github/workflows/monaco-editor.yml index 155e18b38cf..c62fd2b2697 100644 --- a/.github/workflows/monaco-editor.yml +++ b/.github/workflows/monaco-editor.yml @@ -20,7 +20,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version-file: .nvmrc diff --git a/.github/workflows/telemetry.yml b/.github/workflows/telemetry.yml index ab1559d8fa6..d463a0e2eca 100644 --- a/.github/workflows/telemetry.yml +++ b/.github/workflows/telemetry.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: 'actions/checkout@v4' - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: node-version: 'lts/*'