Merge remote-tracking branch 'origin/main' into ai-codefixes

This commit is contained in:
Nathan Shively-Sanders 2023-09-21 08:40:31 -07:00
commit 32bb21550c
106 changed files with 3300 additions and 715 deletions

View file

@ -1528,11 +1528,12 @@ begin
begin
CreateMutex('{#AppMutex}-ready');
Log('Checking whether application is still running...');
while (CheckForMutexes('{#AppMutex}')) do
begin
Log('Application is still running, waiting');
Sleep(1000)
end;
Log('Application appears not to be running.');
StopTunnelServiceIfNeeded();

View file

@ -3006,7 +3006,7 @@
},
"dependencies": {
"@joaomoreno/unique-names-generator": "^5.1.0",
"@vscode/extension-telemetry": "^0.8.4",
"@vscode/extension-telemetry": "^0.8.5",
"@vscode/iconv-lite-umd": "0.7.0",
"byline": "^5.0.0",
"file-type": "16.5.4",

View file

@ -306,10 +306,10 @@
resolved "https://registry.yarnpkg.com/@types/which/-/which-3.0.0.tgz#849afdd9fdcb0b67339b9cfc80fa6ea4e0253fc5"
integrity sha512-ASCxdbsrwNfSMXALlC3Decif9rwDMu+80KGp5zI2RLRotfMsTv7fHL8W8VDp24wymzDyIFudhUeSCugrgRFfHQ==
"@vscode/extension-telemetry@^0.8.4":
version "0.8.4"
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.8.4.tgz#c078c6f55df1c9e0592de3b4ce0f685dd345bfe7"
integrity sha512-UqM9+KZDDK3MyoHTsg6XNM+XO6pweQxzCpqJz33BoBEYAGsbBviRYcVpJglgay2oReuDD2pOI1Nio3BKNDLhWA==
"@vscode/extension-telemetry@^0.8.5":
version "0.8.5"
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.8.5.tgz#3db305be907c01656160e25d91f5d2840175d199"
integrity sha512-YFKANBT2F3qdWQstjcr40XX8BLsdKlKM7a7YPi/jNuMjuiPhb1Jn7YsDR3WZaVEzAqeqGy4gzXsFCBbuZ+L1Tg==
dependencies:
"@microsoft/1ds-core-js" "^3.2.13"
"@microsoft/1ds-post-js" "^3.2.13"

View file

@ -60,7 +60,7 @@
},
"dependencies": {
"node-fetch": "2.6.7",
"@vscode/extension-telemetry": "^0.8.4",
"@vscode/extension-telemetry": "^0.8.5",
"vscode-tas-client": "^0.1.47"
},
"devDependencies": {

View file

@ -282,10 +282,10 @@
resolved "https://registry.yarnpkg.com/@types/shimmer/-/shimmer-1.0.2.tgz#93eb2c243c351f3f17d5c580c7467ae5d686b65f"
integrity sha512-dKkr1bTxbEsFlh2ARpKzcaAmsYixqt9UyCdoEZk8rHyE4iQYcDCyvSjDSf7JUWJHlJiTtbIoQjxKh6ViywqDAg==
"@vscode/extension-telemetry@^0.8.4":
version "0.8.4"
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.8.4.tgz#c078c6f55df1c9e0592de3b4ce0f685dd345bfe7"
integrity sha512-UqM9+KZDDK3MyoHTsg6XNM+XO6pweQxzCpqJz33BoBEYAGsbBviRYcVpJglgay2oReuDD2pOI1Nio3BKNDLhWA==
"@vscode/extension-telemetry@^0.8.5":
version "0.8.5"
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.8.5.tgz#3db305be907c01656160e25d91f5d2840175d199"
integrity sha512-YFKANBT2F3qdWQstjcr40XX8BLsdKlKM7a7YPi/jNuMjuiPhb1Jn7YsDR3WZaVEzAqeqGy4gzXsFCBbuZ+L1Tg==
dependencies:
"@microsoft/1ds-core-js" "^3.2.13"
"@microsoft/1ds-post-js" "^3.2.13"

View file

@ -183,7 +183,7 @@
"@octokit/graphql-schema": "14.4.0",
"@octokit/rest": "19.0.4",
"tunnel": "^0.0.6",
"@vscode/extension-telemetry": "^0.8.4"
"@vscode/extension-telemetry": "^0.8.5"
},
"devDependencies": {
"@types/node": "18.x"

View file

@ -386,10 +386,10 @@
resolved "https://registry.yarnpkg.com/@types/shimmer/-/shimmer-1.0.2.tgz#93eb2c243c351f3f17d5c580c7467ae5d686b65f"
integrity sha512-dKkr1bTxbEsFlh2ARpKzcaAmsYixqt9UyCdoEZk8rHyE4iQYcDCyvSjDSf7JUWJHlJiTtbIoQjxKh6ViywqDAg==
"@vscode/extension-telemetry@^0.8.4":
version "0.8.4"
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.8.4.tgz#c078c6f55df1c9e0592de3b4ce0f685dd345bfe7"
integrity sha512-UqM9+KZDDK3MyoHTsg6XNM+XO6pweQxzCpqJz33BoBEYAGsbBviRYcVpJglgay2oReuDD2pOI1Nio3BKNDLhWA==
"@vscode/extension-telemetry@^0.8.5":
version "0.8.5"
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.8.5.tgz#3db305be907c01656160e25d91f5d2840175d199"
integrity sha512-YFKANBT2F3qdWQstjcr40XX8BLsdKlKM7a7YPi/jNuMjuiPhb1Jn7YsDR3WZaVEzAqeqGy4gzXsFCBbuZ+L1Tg==
dependencies:
"@microsoft/1ds-core-js" "^3.2.13"
"@microsoft/1ds-post-js" "^3.2.13"

View file

@ -258,7 +258,7 @@
]
},
"dependencies": {
"@vscode/extension-telemetry": "^0.8.4",
"@vscode/extension-telemetry": "^0.8.5",
"vscode-languageclient": "^8.2.0-next.3",
"vscode-uri": "^3.0.7"
},

View file

@ -269,10 +269,10 @@
resolved "https://registry.yarnpkg.com/@types/shimmer/-/shimmer-1.0.2.tgz#93eb2c243c351f3f17d5c580c7467ae5d686b65f"
integrity sha512-dKkr1bTxbEsFlh2ARpKzcaAmsYixqt9UyCdoEZk8rHyE4iQYcDCyvSjDSf7JUWJHlJiTtbIoQjxKh6ViywqDAg==
"@vscode/extension-telemetry@^0.8.4":
version "0.8.4"
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.8.4.tgz#c078c6f55df1c9e0592de3b4ce0f685dd345bfe7"
integrity sha512-UqM9+KZDDK3MyoHTsg6XNM+XO6pweQxzCpqJz33BoBEYAGsbBviRYcVpJglgay2oReuDD2pOI1Nio3BKNDLhWA==
"@vscode/extension-telemetry@^0.8.5":
version "0.8.5"
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.8.5.tgz#3db305be907c01656160e25d91f5d2840175d199"
integrity sha512-YFKANBT2F3qdWQstjcr40XX8BLsdKlKM7a7YPi/jNuMjuiPhb1Jn7YsDR3WZaVEzAqeqGy4gzXsFCBbuZ+L1Tg==
dependencies:
"@microsoft/1ds-core-js" "^3.2.13"
"@microsoft/1ds-post-js" "^3.2.13"

View file

@ -158,7 +158,7 @@
]
},
"dependencies": {
"@vscode/extension-telemetry": "^0.8.4",
"@vscode/extension-telemetry": "^0.8.5",
"request-light": "^0.7.0",
"vscode-languageclient": "^8.2.0-next.3"
},

View file

@ -269,10 +269,10 @@
resolved "https://registry.yarnpkg.com/@types/shimmer/-/shimmer-1.0.2.tgz#93eb2c243c351f3f17d5c580c7467ae5d686b65f"
integrity sha512-dKkr1bTxbEsFlh2ARpKzcaAmsYixqt9UyCdoEZk8rHyE4iQYcDCyvSjDSf7JUWJHlJiTtbIoQjxKh6ViywqDAg==
"@vscode/extension-telemetry@^0.8.4":
version "0.8.4"
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.8.4.tgz#c078c6f55df1c9e0592de3b4ce0f685dd345bfe7"
integrity sha512-UqM9+KZDDK3MyoHTsg6XNM+XO6pweQxzCpqJz33BoBEYAGsbBviRYcVpJglgay2oReuDD2pOI1Nio3BKNDLhWA==
"@vscode/extension-telemetry@^0.8.5":
version "0.8.5"
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.8.5.tgz#3db305be907c01656160e25d91f5d2840175d199"
integrity sha512-YFKANBT2F3qdWQstjcr40XX8BLsdKlKM7a7YPi/jNuMjuiPhb1Jn7YsDR3WZaVEzAqeqGy4gzXsFCBbuZ+L1Tg==
dependencies:
"@microsoft/1ds-core-js" "^3.2.13"
"@microsoft/1ds-post-js" "^3.2.13"

View file

@ -726,7 +726,7 @@
"watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose"
},
"dependencies": {
"@vscode/extension-telemetry": "^0.8.4",
"@vscode/extension-telemetry": "^0.8.5",
"dompurify": "^3.0.5",
"highlight.js": "^11.8.0",
"markdown-it": "^12.3.2",

View file

@ -321,10 +321,10 @@
resolved "https://registry.yarnpkg.com/@types/vscode-webview/-/vscode-webview-1.57.0.tgz#bad5194d45ae8d03afc1c0f67f71ff5e7a243bbf"
integrity sha512-x3Cb/SMa1IwRHfSvKaZDZOTh4cNoG505c3NjTqGlMC082m++x/ETUmtYniDsw6SSmYzZXO8KBNhYxR0+VqymqA==
"@vscode/extension-telemetry@^0.8.4":
version "0.8.4"
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.8.4.tgz#c078c6f55df1c9e0592de3b4ce0f685dd345bfe7"
integrity sha512-UqM9+KZDDK3MyoHTsg6XNM+XO6pweQxzCpqJz33BoBEYAGsbBviRYcVpJglgay2oReuDD2pOI1Nio3BKNDLhWA==
"@vscode/extension-telemetry@^0.8.5":
version "0.8.5"
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.8.5.tgz#3db305be907c01656160e25d91f5d2840175d199"
integrity sha512-YFKANBT2F3qdWQstjcr40XX8BLsdKlKM7a7YPi/jNuMjuiPhb1Jn7YsDR3WZaVEzAqeqGy4gzXsFCBbuZ+L1Tg==
dependencies:
"@microsoft/1ds-core-js" "^3.2.13"
"@microsoft/1ds-post-js" "^3.2.13"

View file

@ -126,7 +126,7 @@
"watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose"
},
"dependencies": {
"@vscode/extension-telemetry": "^0.8.4",
"@vscode/extension-telemetry": "^0.8.5",
"vscode-uri": "^3.0.6"
},
"repository": {

View file

@ -264,10 +264,10 @@
resolved "https://registry.yarnpkg.com/@types/shimmer/-/shimmer-1.0.2.tgz#93eb2c243c351f3f17d5c580c7467ae5d686b65f"
integrity sha512-dKkr1bTxbEsFlh2ARpKzcaAmsYixqt9UyCdoEZk8rHyE4iQYcDCyvSjDSf7JUWJHlJiTtbIoQjxKh6ViywqDAg==
"@vscode/extension-telemetry@^0.8.4":
version "0.8.4"
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.8.4.tgz#c078c6f55df1c9e0592de3b4ce0f685dd345bfe7"
integrity sha512-UqM9+KZDDK3MyoHTsg6XNM+XO6pweQxzCpqJz33BoBEYAGsbBviRYcVpJglgay2oReuDD2pOI1Nio3BKNDLhWA==
"@vscode/extension-telemetry@^0.8.5":
version "0.8.5"
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.8.5.tgz#3db305be907c01656160e25d91f5d2840175d199"
integrity sha512-YFKANBT2F3qdWQstjcr40XX8BLsdKlKM7a7YPi/jNuMjuiPhb1Jn7YsDR3WZaVEzAqeqGy4gzXsFCBbuZ+L1Tg==
dependencies:
"@microsoft/1ds-core-js" "^3.2.13"
"@microsoft/1ds-post-js" "^3.2.13"

View file

@ -166,7 +166,7 @@
}
},
"dependencies": {
"@vscode/extension-telemetry": "^0.8.4"
"@vscode/extension-telemetry": "^0.8.5"
},
"devDependencies": {
"@types/node": "18.x"

View file

@ -269,10 +269,10 @@
resolved "https://registry.yarnpkg.com/@types/shimmer/-/shimmer-1.0.2.tgz#93eb2c243c351f3f17d5c580c7467ae5d686b65f"
integrity sha512-dKkr1bTxbEsFlh2ARpKzcaAmsYixqt9UyCdoEZk8rHyE4iQYcDCyvSjDSf7JUWJHlJiTtbIoQjxKh6ViywqDAg==
"@vscode/extension-telemetry@^0.8.4":
version "0.8.4"
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.8.4.tgz#c078c6f55df1c9e0592de3b4ce0f685dd345bfe7"
integrity sha512-UqM9+KZDDK3MyoHTsg6XNM+XO6pweQxzCpqJz33BoBEYAGsbBviRYcVpJglgay2oReuDD2pOI1Nio3BKNDLhWA==
"@vscode/extension-telemetry@^0.8.5":
version "0.8.5"
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.8.5.tgz#3db305be907c01656160e25d91f5d2840175d199"
integrity sha512-YFKANBT2F3qdWQstjcr40XX8BLsdKlKM7a7YPi/jNuMjuiPhb1Jn7YsDR3WZaVEzAqeqGy4gzXsFCBbuZ+L1Tg==
dependencies:
"@microsoft/1ds-core-js" "^3.2.13"
"@microsoft/1ds-post-js" "^3.2.13"

View file

@ -118,7 +118,7 @@
"dependencies": {
"node-fetch": "2.6.7",
"@azure/ms-rest-azure-env": "^2.0.0",
"@vscode/extension-telemetry": "^0.8.4"
"@vscode/extension-telemetry": "^0.8.5"
},
"repository": {
"type": "git",

View file

@ -306,10 +306,10 @@
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.0.tgz#165aae4819ad2174a17476dbe66feebd549556c0"
integrity sha512-xSQfNcvOiE5f9dyd4Kzxbof1aTrLobL278pGLKOZI6esGfZ7ts9Ka16CzIN6Y8hFHE1C7jIBZokULhK1bOgjRw==
"@vscode/extension-telemetry@^0.8.4":
version "0.8.4"
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.8.4.tgz#c078c6f55df1c9e0592de3b4ce0f685dd345bfe7"
integrity sha512-UqM9+KZDDK3MyoHTsg6XNM+XO6pweQxzCpqJz33BoBEYAGsbBviRYcVpJglgay2oReuDD2pOI1Nio3BKNDLhWA==
"@vscode/extension-telemetry@^0.8.5":
version "0.8.5"
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.8.5.tgz#3db305be907c01656160e25d91f5d2840175d199"
integrity sha512-YFKANBT2F3qdWQstjcr40XX8BLsdKlKM7a7YPi/jNuMjuiPhb1Jn7YsDR3WZaVEzAqeqGy4gzXsFCBbuZ+L1Tg==
dependencies:
"@microsoft/1ds-core-js" "^3.2.13"
"@microsoft/1ds-post-js" "^3.2.13"

View file

@ -66,7 +66,7 @@
"watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose"
},
"dependencies": {
"@vscode/extension-telemetry": "^0.8.4"
"@vscode/extension-telemetry": "^0.8.5"
},
"devDependencies": {
"@types/vscode-webview": "^1.57.0",

View file

@ -269,10 +269,10 @@
resolved "https://registry.yarnpkg.com/@types/vscode-webview/-/vscode-webview-1.57.0.tgz#bad5194d45ae8d03afc1c0f67f71ff5e7a243bbf"
integrity sha512-x3Cb/SMa1IwRHfSvKaZDZOTh4cNoG505c3NjTqGlMC082m++x/ETUmtYniDsw6SSmYzZXO8KBNhYxR0+VqymqA==
"@vscode/extension-telemetry@^0.8.4":
version "0.8.4"
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.8.4.tgz#c078c6f55df1c9e0592de3b4ce0f685dd345bfe7"
integrity sha512-UqM9+KZDDK3MyoHTsg6XNM+XO6pweQxzCpqJz33BoBEYAGsbBviRYcVpJglgay2oReuDD2pOI1Nio3BKNDLhWA==
"@vscode/extension-telemetry@^0.8.5":
version "0.8.5"
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.8.5.tgz#3db305be907c01656160e25d91f5d2840175d199"
integrity sha512-YFKANBT2F3qdWQstjcr40XX8BLsdKlKM7a7YPi/jNuMjuiPhb1Jn7YsDR3WZaVEzAqeqGy4gzXsFCBbuZ+L1Tg==
dependencies:
"@microsoft/1ds-core-js" "^3.2.13"
"@microsoft/1ds-post-js" "^3.2.13"

View file

@ -34,7 +34,7 @@
"Programming Languages"
],
"dependencies": {
"@vscode/extension-telemetry": "^0.8.4",
"@vscode/extension-telemetry": "^0.8.5",
"@vscode/sync-api-client": "^0.7.2",
"@vscode/sync-api-common": "^0.7.2",
"@vscode/sync-api-service": "^0.7.3",

View file

@ -253,10 +253,10 @@
resolved "https://registry.yarnpkg.com/@types/shimmer/-/shimmer-1.0.2.tgz#93eb2c243c351f3f17d5c580c7467ae5d686b65f"
integrity sha512-dKkr1bTxbEsFlh2ARpKzcaAmsYixqt9UyCdoEZk8rHyE4iQYcDCyvSjDSf7JUWJHlJiTtbIoQjxKh6ViywqDAg==
"@vscode/extension-telemetry@^0.8.4":
version "0.8.4"
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.8.4.tgz#c078c6f55df1c9e0592de3b4ce0f685dd345bfe7"
integrity sha512-UqM9+KZDDK3MyoHTsg6XNM+XO6pweQxzCpqJz33BoBEYAGsbBviRYcVpJglgay2oReuDD2pOI1Nio3BKNDLhWA==
"@vscode/extension-telemetry@^0.8.5":
version "0.8.5"
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.8.5.tgz#3db305be907c01656160e25d91f5d2840175d199"
integrity sha512-YFKANBT2F3qdWQstjcr40XX8BLsdKlKM7a7YPi/jNuMjuiPhb1Jn7YsDR3WZaVEzAqeqGy4gzXsFCBbuZ+L1Tg==
dependencies:
"@microsoft/1ds-core-js" "^3.2.13"
"@microsoft/1ds-post-js" "^3.2.13"

View file

@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.83.0",
"distro": "5a794f00ac87e37b6abea46ccf030aeb4b323aeb",
"distro": "f4e12eeb4a251bbec78e035eaeb7f7c5dd372a40",
"author": {
"name": "Microsoft Corporation"
},
@ -94,14 +94,14 @@
"vscode-oniguruma": "1.7.0",
"vscode-regexpp": "^3.1.0",
"vscode-textmate": "9.0.0",
"xterm": "5.4.0-beta.19",
"xterm-addon-canvas": "0.6.0-beta.19",
"xterm": "5.4.0-beta.27",
"xterm-addon-canvas": "0.6.0-beta.27",
"xterm-addon-image": "0.6.0-beta.21",
"xterm-addon-search": "0.14.0-beta.18",
"xterm-addon-serialize": "0.12.0-beta.18",
"xterm-addon-unicode11": "0.7.0-beta.18",
"xterm-addon-webgl": "0.17.0-beta.18",
"xterm-headless": "5.4.0-beta.19",
"xterm-addon-search": "0.14.0-beta.26",
"xterm-addon-serialize": "0.12.0-beta.26",
"xterm-addon-unicode11": "0.7.0-beta.26",
"xterm-addon-webgl": "0.17.0-beta.26",
"xterm-headless": "5.4.0-beta.27",
"yauzl": "^2.9.2",
"yazl": "^2.4.3"
},

View file

@ -26,14 +26,14 @@
"vscode-oniguruma": "1.7.0",
"vscode-regexpp": "^3.1.0",
"vscode-textmate": "9.0.0",
"xterm": "5.4.0-beta.19",
"xterm-addon-canvas": "0.6.0-beta.19",
"xterm": "5.4.0-beta.27",
"xterm-addon-canvas": "0.6.0-beta.27",
"xterm-addon-image": "0.6.0-beta.21",
"xterm-addon-search": "0.14.0-beta.18",
"xterm-addon-serialize": "0.12.0-beta.18",
"xterm-addon-unicode11": "0.7.0-beta.18",
"xterm-addon-webgl": "0.17.0-beta.18",
"xterm-headless": "5.4.0-beta.19",
"xterm-addon-search": "0.14.0-beta.26",
"xterm-addon-serialize": "0.12.0-beta.26",
"xterm-addon-unicode11": "0.7.0-beta.26",
"xterm-addon-webgl": "0.17.0-beta.26",
"xterm-headless": "5.4.0-beta.27",
"yauzl": "^2.9.2",
"yazl": "^2.4.3"
}

View file

@ -11,11 +11,11 @@
"tas-client-umd": "0.1.8",
"vscode-oniguruma": "1.7.0",
"vscode-textmate": "9.0.0",
"xterm": "5.4.0-beta.19",
"xterm-addon-canvas": "0.6.0-beta.19",
"xterm": "5.4.0-beta.27",
"xterm-addon-canvas": "0.6.0-beta.27",
"xterm-addon-image": "0.6.0-beta.21",
"xterm-addon-search": "0.14.0-beta.18",
"xterm-addon-unicode11": "0.7.0-beta.18",
"xterm-addon-webgl": "0.17.0-beta.18"
"xterm-addon-search": "0.14.0-beta.26",
"xterm-addon-unicode11": "0.7.0-beta.26",
"xterm-addon-webgl": "0.17.0-beta.26"
}
}

View file

@ -68,32 +68,32 @@ vscode-textmate@9.0.0:
resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-9.0.0.tgz#313c6c8792b0507aef35aeb81b6b370b37c44d6c"
integrity sha512-Cl65diFGxz7gpwbav10HqiY/eVYTO1sjQpmRmV991Bj7wAoOAjGQ97PpQcXorDE2Uc4hnGWLY17xme+5t6MlSg==
xterm-addon-canvas@0.6.0-beta.19:
version "0.6.0-beta.19"
resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.6.0-beta.19.tgz#c9e01330a548fbb243d731728e70ae77e6dc8c4f"
integrity sha512-S2tZXqnAqkLA5r40gbOlciR7CLtfztn0lk/ko8Bol08pgSqEzWKF0yadYpsezHRmemvTSmyePCtOTqDrEgFQBA==
xterm-addon-canvas@0.6.0-beta.27:
version "0.6.0-beta.27"
resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.6.0-beta.27.tgz#2517f050d165b093a3c3e564e4420ccc3ccbad75"
integrity sha512-mSxEJKPnXYKkD6/zQLdNH6kB+sr4B+4DMFzntWgxLjHJdyOO95wUSAtBFnhAUez2nNYvXbs/OXpEbdVdO7f2kQ==
xterm-addon-image@0.6.0-beta.21:
version "0.6.0-beta.21"
resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.6.0-beta.21.tgz#e3708bc504c56a23ff31f12a2eeb335331a92aac"
integrity sha512-8/PTaXVPa4kQ0xzVeuZZk10OpbZBj2cgfwhM2B0ChSPvwrk0lX+ksnXdtDKH3tg+JYvo7fIhNXtkr4NwWt7VJQ==
xterm-addon-search@0.14.0-beta.18:
version "0.14.0-beta.18"
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.18.tgz#6298b34d9c590f3e3acdd101eb297eaf8cb1f298"
integrity sha512-1CF2bPz9/vQR+q7OFgjvbBRQ0rUSkiKlwZJMnizgbKl6qx0GNg15T52J+l8zLgg8HavlF8aVps1co1A+N5PPZA==
xterm-addon-search@0.14.0-beta.26:
version "0.14.0-beta.26"
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.26.tgz#2b5b8af31613c896d354c4799624090b11cd601c"
integrity sha512-CghsGO7fJa0efClbgZH20lh/JUaQYgJ1AJTPm8luc/eDc6DWOJblU0MxIABclLgT8lagv9+sOQfO0VIkAITxig==
xterm-addon-unicode11@0.7.0-beta.18:
version "0.7.0-beta.18"
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.7.0-beta.18.tgz#8e47b5be3ce5f07a136ca66919f548680da96648"
integrity sha512-5Zy2Kn7kSYQP2ItPV9HNsX2u6/XajB6uyRZ/tx5U79XjZIOMMtPLki56fiGxBOlyhIHFr96bwRlvYKZcEY1ndQ==
xterm-addon-unicode11@0.7.0-beta.26:
version "0.7.0-beta.26"
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.7.0-beta.26.tgz#f9606231a8f13e57dbdec5e884b044b0813931f5"
integrity sha512-po+z1ayyrkWh8IGXKpbwCLKLKfcjotZVKqowU6PtHuDtJm/J8rlzvV2eJU1WQ/8ezpopU09ibWCvaf1a7EPuxA==
xterm-addon-webgl@0.17.0-beta.18:
version "0.17.0-beta.18"
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.17.0-beta.18.tgz#0ce7b2ed4f1aaefff0eb59dac5c0e2fbba08d9ec"
integrity sha512-F94/+Koo98fAwVr8zFw4vYnmZPKyN6K4ZQTDM2ICozBHtiVWgR2PjhBP8covD6vXwbsnrwq5aipJLbhBMeZ60w==
xterm-addon-webgl@0.17.0-beta.26:
version "0.17.0-beta.26"
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.17.0-beta.26.tgz#aee4a043981d5d303b7112ef7049bc2865e75393"
integrity sha512-N8CuAPZnoDlQ6yV7n4eXQ2ONPr/GdxiwgxrJjNks4CzzHiJREm23FQIv0fCTwKQS5xU3qoc4LlT3vZ1tKGjtQw==
xterm@5.4.0-beta.19:
version "5.4.0-beta.19"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.4.0-beta.19.tgz#5177e37c8e885aa5cbbb0b1972e9df7634a8aa33"
integrity sha512-eM5UmMf3ml8NIBixEwH5CKj5rgwiZhE51W8xhs7js1GIGVusG/1SL7gS6d/n9UlspnAvQtUOIqzc70x887m6jQ==
xterm@5.4.0-beta.27:
version "5.4.0-beta.27"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.4.0-beta.27.tgz#f641ee045a65c9c8967fac534a202062706a8fa9"
integrity sha512-gKqtrjy0RLk2123oFyPw5tkV96jGz4c/JkY8/XUvBXoMVsX4A7rVKpHlmHhmnuK1X5ERAkvCD21YE7LfB8WYkw==

View file

@ -667,45 +667,45 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
xterm-addon-canvas@0.6.0-beta.19:
version "0.6.0-beta.19"
resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.6.0-beta.19.tgz#c9e01330a548fbb243d731728e70ae77e6dc8c4f"
integrity sha512-S2tZXqnAqkLA5r40gbOlciR7CLtfztn0lk/ko8Bol08pgSqEzWKF0yadYpsezHRmemvTSmyePCtOTqDrEgFQBA==
xterm-addon-canvas@0.6.0-beta.27:
version "0.6.0-beta.27"
resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.6.0-beta.27.tgz#2517f050d165b093a3c3e564e4420ccc3ccbad75"
integrity sha512-mSxEJKPnXYKkD6/zQLdNH6kB+sr4B+4DMFzntWgxLjHJdyOO95wUSAtBFnhAUez2nNYvXbs/OXpEbdVdO7f2kQ==
xterm-addon-image@0.6.0-beta.21:
version "0.6.0-beta.21"
resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.6.0-beta.21.tgz#e3708bc504c56a23ff31f12a2eeb335331a92aac"
integrity sha512-8/PTaXVPa4kQ0xzVeuZZk10OpbZBj2cgfwhM2B0ChSPvwrk0lX+ksnXdtDKH3tg+JYvo7fIhNXtkr4NwWt7VJQ==
xterm-addon-search@0.14.0-beta.18:
version "0.14.0-beta.18"
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.18.tgz#6298b34d9c590f3e3acdd101eb297eaf8cb1f298"
integrity sha512-1CF2bPz9/vQR+q7OFgjvbBRQ0rUSkiKlwZJMnizgbKl6qx0GNg15T52J+l8zLgg8HavlF8aVps1co1A+N5PPZA==
xterm-addon-search@0.14.0-beta.26:
version "0.14.0-beta.26"
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.26.tgz#2b5b8af31613c896d354c4799624090b11cd601c"
integrity sha512-CghsGO7fJa0efClbgZH20lh/JUaQYgJ1AJTPm8luc/eDc6DWOJblU0MxIABclLgT8lagv9+sOQfO0VIkAITxig==
xterm-addon-serialize@0.12.0-beta.18:
version "0.12.0-beta.18"
resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.12.0-beta.18.tgz#0d9369acb49fa01f124dfff064a17f4874211f1a"
integrity sha512-RyV6iU/KRC3QN29i3iaWzm33ACbi0gMQW+LjKSFJN/XrNO1QTqfnh2VZp58G32IFG8l2sg/FUs2gXtEB5IMlhA==
xterm-addon-serialize@0.12.0-beta.26:
version "0.12.0-beta.26"
resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.12.0-beta.26.tgz#cb5bd80128e82880369cb012938e14414b182aa1"
integrity sha512-b4lOcttE6lqAF3zB2l8XtDShe5djhl9SueljnVWuG4mYMYPQoiklxFcpY66sjSCIAS6NsbtrL/LGQ/0eZGi+Ig==
xterm-addon-unicode11@0.7.0-beta.18:
version "0.7.0-beta.18"
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.7.0-beta.18.tgz#8e47b5be3ce5f07a136ca66919f548680da96648"
integrity sha512-5Zy2Kn7kSYQP2ItPV9HNsX2u6/XajB6uyRZ/tx5U79XjZIOMMtPLki56fiGxBOlyhIHFr96bwRlvYKZcEY1ndQ==
xterm-addon-unicode11@0.7.0-beta.26:
version "0.7.0-beta.26"
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.7.0-beta.26.tgz#f9606231a8f13e57dbdec5e884b044b0813931f5"
integrity sha512-po+z1ayyrkWh8IGXKpbwCLKLKfcjotZVKqowU6PtHuDtJm/J8rlzvV2eJU1WQ/8ezpopU09ibWCvaf1a7EPuxA==
xterm-addon-webgl@0.17.0-beta.18:
version "0.17.0-beta.18"
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.17.0-beta.18.tgz#0ce7b2ed4f1aaefff0eb59dac5c0e2fbba08d9ec"
integrity sha512-F94/+Koo98fAwVr8zFw4vYnmZPKyN6K4ZQTDM2ICozBHtiVWgR2PjhBP8covD6vXwbsnrwq5aipJLbhBMeZ60w==
xterm-addon-webgl@0.17.0-beta.26:
version "0.17.0-beta.26"
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.17.0-beta.26.tgz#aee4a043981d5d303b7112ef7049bc2865e75393"
integrity sha512-N8CuAPZnoDlQ6yV7n4eXQ2ONPr/GdxiwgxrJjNks4CzzHiJREm23FQIv0fCTwKQS5xU3qoc4LlT3vZ1tKGjtQw==
xterm-headless@5.4.0-beta.19:
version "5.4.0-beta.19"
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.4.0-beta.19.tgz#cdbad09917bdbeeae9197c87663d603fc2794212"
integrity sha512-lLHbZ0DUBoolt4kWCchBAZDlDDdWROwIFxzG8sK389/Z7AlVWsA7kasz2TIPN+l9SC7MrhDdWIFwi1Z0eVODcg==
xterm-headless@5.4.0-beta.27:
version "5.4.0-beta.27"
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.4.0-beta.27.tgz#cfce5f86e83580388238ea204bb451b7ffe94dc9"
integrity sha512-vdrq5eeNMyHZRDw5XR/TPl8oPln0BqbR07akt/fDXMsVg6YwWG+UOnU6GIMj7bJaBed5YkPV9NeBtdsVQn4Lyw==
xterm@5.4.0-beta.19:
version "5.4.0-beta.19"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.4.0-beta.19.tgz#5177e37c8e885aa5cbbb0b1972e9df7634a8aa33"
integrity sha512-eM5UmMf3ml8NIBixEwH5CKj5rgwiZhE51W8xhs7js1GIGVusG/1SL7gS6d/n9UlspnAvQtUOIqzc70x887m6jQ==
xterm@5.4.0-beta.27:
version "5.4.0-beta.27"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.4.0-beta.27.tgz#f641ee045a65c9c8967fac534a202062706a8fa9"
integrity sha512-gKqtrjy0RLk2123oFyPw5tkV96jGz4c/JkY8/XUvBXoMVsX4A7rVKpHlmHhmnuK1X5ERAkvCD21YE7LfB8WYkw==
yallist@^4.0.0:
version "4.0.0"

View file

@ -35,4 +35,5 @@
.icon-select-box .icon-select-id-container .icon-select-id-label .highlight {
color: var(--vscode-list-highlightForeground);
font-weight: bold;
}

View file

@ -19,6 +19,7 @@ import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlighte
export interface IIconSelectBoxOptions {
readonly icons: ThemeIcon[];
readonly inputBoxStyles: IInputBoxStyles;
readonly showIconInfo?: boolean;
}
interface IRenderedIconItem {
@ -46,7 +47,7 @@ export class IconSelectBox extends Disposable {
private scrollableElement: DomScrollableElement | undefined;
private iconIdElement: HighlightedLabel | undefined;
private readonly iconContainerWidth = 36;
private readonly iconContainerHeight = 32;
private readonly iconContainerHeight = 36;
constructor(
private readonly options: IIconSelectBoxOptions,
@ -78,7 +79,10 @@ export class IconSelectBox extends Disposable {
horizontal: ScrollbarVisibility.Hidden,
}));
dom.append(iconSelectBoxContainer, this.scrollableElement.getDomNode());
this.iconIdElement = new HighlightedLabel(dom.append(dom.append(iconSelectBoxContainer, dom.$('.icon-select-id-container')), dom.$('.icon-select-id-label')));
if (this.options.showIconInfo) {
this.iconIdElement = new HighlightedLabel(dom.append(dom.append(iconSelectBoxContainer, dom.$('.icon-select-id-container')), dom.$('.icon-select-id-label')));
}
const iconsDisposables = disposables.add(new MutableDisposable());
iconsDisposables.value = this.renderIcons(this.options.icons, [], iconsContainer);
@ -227,7 +231,7 @@ export class IconSelectBox extends Disposable {
}
if (this.scrollableElement) {
this.scrollableElement.getDomNode().style.height = `${dimension.height - 80}px`;
this.scrollableElement.getDomNode().style.height = `${this.iconIdElement ? dimension.height - 80 : dimension.height - 40}px`;
this.scrollableElement.scanDomNode();
}
}
@ -244,6 +248,12 @@ export class IconSelectBox extends Disposable {
this._onDidSelect.fire(this.renderedIcons[index].icon);
}
clearInput(): void {
if (this.inputBox) {
this.inputBox.value = '';
}
}
focus(): void {
this.inputBox?.focus();
this.focusIcon(0);

View file

@ -6,7 +6,7 @@
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { CancellationError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, DisposableMap, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { Disposable, DisposableMap, DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { extUri as defaultExtUri, IExtUri } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { setTimeout0 } from 'vs/base/common/platform';
@ -490,9 +490,36 @@ export function timeout(millis: number, token?: CancellationToken): CancelablePr
});
}
export function disposableTimeout(handler: () => void, timeout = 0): IDisposable {
const timer = setTimeout(handler, timeout);
return toDisposable(() => clearTimeout(timer));
/**
* Creates a timeout that can be disposed using its returned value.
* @param handler The timeout handler.
* @param timeout An optional timeout in milliseconds.
* @param store An optional {@link DisposableStore} that will have the timeout disposable managed automatically.
*
* @example
* const store = new DisposableStore;
* // Call the timeout after 1000ms at which point it will be automatically
* // evicted from the store.
* const timeoutDisposable = disposableTimeout(() => {}, 1000, store);
*
* if (foo) {
* // Cancel the timeout and evict it from store.
* timeoutDisposable.dispose();
* }
*/
export function disposableTimeout(handler: () => void, timeout = 0, store?: DisposableStore): IDisposable {
const timer = setTimeout(() => {
handler();
if (store) {
disposable.dispose();
}
}, timeout);
const disposable = toDisposable(() => {
clearTimeout(timer);
store?.deleteAndLeak(disposable);
});
store?.add(disposable);
return disposable;
}
/**

View file

@ -430,6 +430,34 @@ export class DisposableStore implements IDisposable {
return o;
}
/**
* Deletes a disposable from store and disposes of it. This will not throw or warn and proceed to dispose the
* disposable even when the disposable is not part in the store.
*/
public delete<T extends IDisposable>(o: T): void {
if (!o) {
return;
}
if ((o as unknown as DisposableStore) === this) {
throw new Error('Cannot dispose a disposable on itself!');
}
this._toDispose.delete(o);
o.dispose();
}
/**
* Deletes the value from the store, but does not dispose it.
*/
public deleteAndLeak<T extends IDisposable>(o: T): void {
if (!o) {
return;
}
if (this._toDispose.has(o)) {
this._toDispose.delete(o);
setParentOfDisposable(o, null);
}
}
}
/**

View file

@ -12,6 +12,7 @@ import { Event } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
import { DisposableStore } from 'vs/base/common/lifecycle';
suite('Async', () => {
@ -807,6 +808,65 @@ suite('Async', () => {
});
});
suite('disposableTimeout', () => {
test('handler only success', async () => {
let cb = false;
const t = async.disposableTimeout(() => cb = true);
await async.timeout(0);
assert.strictEqual(cb, true);
t.dispose();
});
test('handler only cancel', async () => {
let cb = false;
const t = async.disposableTimeout(() => cb = true);
t.dispose();
await async.timeout(0);
assert.strictEqual(cb, false);
});
test('store managed success', async () => {
let cb = false;
const s = new DisposableStore();
async.disposableTimeout(() => cb = true, 0, s);
await async.timeout(0);
assert.strictEqual(cb, true);
s.dispose();
});
test('store managed cancel via disposable', async () => {
let cb = false;
const s = new DisposableStore();
const t = async.disposableTimeout(() => cb = true, 0, s);
t.dispose();
await async.timeout(0);
assert.strictEqual(cb, false);
s.dispose();
});
test('store managed cancel via store', async () => {
let cb = false;
const s = new DisposableStore();
async.disposableTimeout(() => cb = true, 0, s);
s.dispose();
await async.timeout(0);
assert.strictEqual(cb, false);
});
});
test('raceCancellation', async () => {
const cts = store.add(new CancellationTokenSource());
const ctsTimeout = store.add(new CancellationTokenSource());

View file

@ -172,6 +172,52 @@ suite('DisposableStore', () => {
assert.strictEqual((thrownError as AggregateError).errors[0].message, 'I am error 1');
assert.strictEqual((thrownError as AggregateError).errors[1].message, 'I am error 2');
});
test('delete should evict and dispose of the disposables', () => {
const disposedValues = new Set<number>();
const disposables: IDisposable[] = [
toDisposable(() => { disposedValues.add(1); }),
toDisposable(() => { disposedValues.add(2); })
];
const store = new DisposableStore();
store.add(disposables[0]);
store.add(disposables[1]);
store.delete(disposables[0]);
assert.ok(disposedValues.has(1));
assert.ok(!disposedValues.has(2));
store.dispose();
assert.ok(disposedValues.has(1));
assert.ok(disposedValues.has(2));
});
test('deleteAndLeak should evict and not dispose of the disposables', () => {
const disposedValues = new Set<number>();
const disposables: IDisposable[] = [
toDisposable(() => { disposedValues.add(1); }),
toDisposable(() => { disposedValues.add(2); })
];
const store = new DisposableStore();
store.add(disposables[0]);
store.add(disposables[1]);
store.deleteAndLeak(disposables[0]);
assert.ok(!disposedValues.has(1));
assert.ok(!disposedValues.has(2));
store.dispose();
assert.ok(!disposedValues.has(1));
assert.ok(disposedValues.has(2));
disposables[0].dispose();
});
});
suite('Reference Collection', () => {

View file

@ -448,8 +448,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi
})),
token);
const edits = coalesce(results ?? []);
sortEditsByYieldTo(edits);
return edits;
return sortEditsByYieldTo(edits);
}
private async applyDefaultPasteHandler(dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined, token: CancellationToken) {

View file

@ -9,6 +9,7 @@ import { HiddenRangeModel } from 'vs/editor/contrib/folding/browser/hiddenRangeM
import { computeRanges } from 'vs/editor/contrib/folding/browser/indentRangeProvider';
import { createTextModel } from 'vs/editor/test/common/testTextModel';
import { TestDecorationProvider } from './foldingModel.test';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
interface ExpectedRange {
@ -41,59 +42,60 @@ suite('Hidden Range Model', () => {
const textModel = createTextModel(lines.join('\n'));
const foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel));
const hiddenRangeModel = new HiddenRangeModel(foldingModel);
try {
assert.strictEqual(hiddenRangeModel.hasRanges(), false);
assert.strictEqual(hiddenRangeModel.hasRanges(), false);
const ranges = computeRanges(textModel, false, undefined);
foldingModel.update(ranges);
const ranges = computeRanges(textModel, false, undefined);
foldingModel.update(ranges);
foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(1)!, foldingModel.getRegionAtLine(6)!]);
assertRanges(hiddenRangeModel.hiddenRanges, [r(2, 3), r(7, 7)]);
foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(1)!, foldingModel.getRegionAtLine(6)!]);
assertRanges(hiddenRangeModel.hiddenRanges, [r(2, 3), r(7, 7)]);
assert.strictEqual(hiddenRangeModel.hasRanges(), true);
assert.strictEqual(hiddenRangeModel.isHidden(1), false);
assert.strictEqual(hiddenRangeModel.isHidden(2), true);
assert.strictEqual(hiddenRangeModel.isHidden(3), true);
assert.strictEqual(hiddenRangeModel.isHidden(4), false);
assert.strictEqual(hiddenRangeModel.isHidden(5), false);
assert.strictEqual(hiddenRangeModel.isHidden(6), false);
assert.strictEqual(hiddenRangeModel.isHidden(7), true);
assert.strictEqual(hiddenRangeModel.isHidden(8), false);
assert.strictEqual(hiddenRangeModel.isHidden(9), false);
assert.strictEqual(hiddenRangeModel.isHidden(10), false);
assert.strictEqual(hiddenRangeModel.hasRanges(), true);
assert.strictEqual(hiddenRangeModel.isHidden(1), false);
assert.strictEqual(hiddenRangeModel.isHidden(2), true);
assert.strictEqual(hiddenRangeModel.isHidden(3), true);
assert.strictEqual(hiddenRangeModel.isHidden(4), false);
assert.strictEqual(hiddenRangeModel.isHidden(5), false);
assert.strictEqual(hiddenRangeModel.isHidden(6), false);
assert.strictEqual(hiddenRangeModel.isHidden(7), true);
assert.strictEqual(hiddenRangeModel.isHidden(8), false);
assert.strictEqual(hiddenRangeModel.isHidden(9), false);
assert.strictEqual(hiddenRangeModel.isHidden(10), false);
foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(4)!]);
assertRanges(hiddenRangeModel.hiddenRanges, [r(2, 3), r(5, 9)]);
foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(4)!]);
assertRanges(hiddenRangeModel.hiddenRanges, [r(2, 3), r(5, 9)]);
assert.strictEqual(hiddenRangeModel.hasRanges(), true);
assert.strictEqual(hiddenRangeModel.isHidden(1), false);
assert.strictEqual(hiddenRangeModel.isHidden(2), true);
assert.strictEqual(hiddenRangeModel.isHidden(3), true);
assert.strictEqual(hiddenRangeModel.isHidden(4), false);
assert.strictEqual(hiddenRangeModel.isHidden(5), true);
assert.strictEqual(hiddenRangeModel.isHidden(6), true);
assert.strictEqual(hiddenRangeModel.isHidden(7), true);
assert.strictEqual(hiddenRangeModel.isHidden(8), true);
assert.strictEqual(hiddenRangeModel.isHidden(9), true);
assert.strictEqual(hiddenRangeModel.isHidden(10), false);
foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(1)!, foldingModel.getRegionAtLine(6)!, foldingModel.getRegionAtLine(4)!]);
assertRanges(hiddenRangeModel.hiddenRanges, []);
assert.strictEqual(hiddenRangeModel.hasRanges(), false);
assert.strictEqual(hiddenRangeModel.isHidden(1), false);
assert.strictEqual(hiddenRangeModel.isHidden(2), false);
assert.strictEqual(hiddenRangeModel.isHidden(3), false);
assert.strictEqual(hiddenRangeModel.isHidden(4), false);
assert.strictEqual(hiddenRangeModel.isHidden(5), false);
assert.strictEqual(hiddenRangeModel.isHidden(6), false);
assert.strictEqual(hiddenRangeModel.isHidden(7), false);
assert.strictEqual(hiddenRangeModel.isHidden(8), false);
assert.strictEqual(hiddenRangeModel.isHidden(9), false);
assert.strictEqual(hiddenRangeModel.isHidden(10), false);
textModel.dispose();
assert.strictEqual(hiddenRangeModel.hasRanges(), true);
assert.strictEqual(hiddenRangeModel.isHidden(1), false);
assert.strictEqual(hiddenRangeModel.isHidden(2), true);
assert.strictEqual(hiddenRangeModel.isHidden(3), true);
assert.strictEqual(hiddenRangeModel.isHidden(4), false);
assert.strictEqual(hiddenRangeModel.isHidden(5), true);
assert.strictEqual(hiddenRangeModel.isHidden(6), true);
assert.strictEqual(hiddenRangeModel.isHidden(7), true);
assert.strictEqual(hiddenRangeModel.isHidden(8), true);
assert.strictEqual(hiddenRangeModel.isHidden(9), true);
assert.strictEqual(hiddenRangeModel.isHidden(10), false);
foldingModel.toggleCollapseState([foldingModel.getRegionAtLine(1)!, foldingModel.getRegionAtLine(6)!, foldingModel.getRegionAtLine(4)!]);
assertRanges(hiddenRangeModel.hiddenRanges, []);
assert.strictEqual(hiddenRangeModel.hasRanges(), false);
assert.strictEqual(hiddenRangeModel.isHidden(1), false);
assert.strictEqual(hiddenRangeModel.isHidden(2), false);
assert.strictEqual(hiddenRangeModel.isHidden(3), false);
assert.strictEqual(hiddenRangeModel.isHidden(4), false);
assert.strictEqual(hiddenRangeModel.isHidden(5), false);
assert.strictEqual(hiddenRangeModel.isHidden(6), false);
assert.strictEqual(hiddenRangeModel.isHidden(7), false);
assert.strictEqual(hiddenRangeModel.isHidden(8), false);
assert.strictEqual(hiddenRangeModel.isHidden(9), false);
assert.strictEqual(hiddenRangeModel.isHidden(10), false);
} finally {
textModel.dispose();
hiddenRangeModel.dispose();
}
});
ensureNoDisposablesAreLeakedInTestSuite();
});

View file

@ -9,6 +9,7 @@ import { FoldingContext, FoldingRange, FoldingRangeProvider, ProviderResult } fr
import { SyntaxRangeProvider } from 'vs/editor/contrib/folding/browser/syntaxRangeProvider';
import { createTextModel } from 'vs/editor/test/common/testTextModel';
import { FoldingLimitReporter } from 'vs/editor/contrib/folding/browser/folding';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
interface IndentRange {
start: number;
@ -77,15 +78,20 @@ suite('Syntax folding', () => {
async function assertLimit(maxEntries: number, expectedRanges: IndentRange[], message: string) {
let reported: number | false = false;
const foldingRangesLimit: FoldingLimitReporter = { limit: maxEntries, update: (computed, limited) => reported = limited };
const indentRanges = await new SyntaxRangeProvider(model, providers, () => { }, foldingRangesLimit, undefined).compute(CancellationToken.None);
const actual: IndentRange[] = [];
if (indentRanges) {
for (let i = 0; i < indentRanges.length; i++) {
actual.push({ start: indentRanges.getStartLineNumber(i), end: indentRanges.getEndLineNumber(i) });
const syntaxRangeProvider = new SyntaxRangeProvider(model, providers, () => { }, foldingRangesLimit, undefined);
try {
const indentRanges = await syntaxRangeProvider.compute(CancellationToken.None);
const actual: IndentRange[] = [];
if (indentRanges) {
for (let i = 0; i < indentRanges.length; i++) {
actual.push({ start: indentRanges.getStartLineNumber(i), end: indentRanges.getEndLineNumber(i) });
}
assert.equal(reported, 9 <= maxEntries ? false : maxEntries, 'limited');
}
assert.equal(reported, 9 <= maxEntries ? false : maxEntries, 'limited');
assert.deepStrictEqual(actual, expectedRanges, message);
} finally {
syntaxRangeProvider.dispose();
}
assert.deepStrictEqual(actual, expectedRanges, message);
}
@ -103,5 +109,5 @@ suite('Syntax folding', () => {
model.dispose();
});
ensureNoDisposablesAreLeakedInTestSuite();
});

View file

@ -6,7 +6,7 @@
import { app, Event as ElectronEvent } from 'electron';
import { disposableTimeout } from 'vs/base/common/async';
import { Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { Disposable } from 'vs/base/common/lifecycle';
import { isWindows } from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
@ -26,12 +26,10 @@ import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
* that calls VSCode with the `open-url` command line argument
* (https://github.com/microsoft/vscode/pull/56727)
*/
export class ElectronURLListener {
export class ElectronURLListener extends Disposable {
private uris: IProtocolUrl[] = [];
private retryCount = 0;
private flushDisposable: IDisposable = Disposable.None;
private readonly disposables = new DisposableStore();
constructor(
initialProtocolUrls: IProtocolUrl[] | undefined,
@ -41,6 +39,8 @@ export class ElectronURLListener {
productService: IProductService,
private readonly logService: ILogService
) {
super();
if (initialProtocolUrls) {
logService.trace('ElectronURLListener initialUrisToHandle:', initialProtocolUrls.map(url => url.originalUrl));
@ -64,7 +64,7 @@ export class ElectronURLListener {
return url;
});
this.disposables.add(onOpenElectronUrl(url => {
this._register(onOpenElectronUrl(url => {
const uri = this.uriFromRawUrl(url);
if (!uri) {
return;
@ -85,7 +85,7 @@ export class ElectronURLListener {
} else {
logService.trace('ElectronURLListener: waiting for window to be ready to handle URLs...');
Event.once(windowsMainService.onDidSignalReadyWindow)(this.flush, this, this.disposables);
this._register(Event.once(windowsMainService.onDidSignalReadyWindow)(this.flush));
}
}
@ -124,11 +124,6 @@ export class ElectronURLListener {
}
this.uris = uris;
this.flushDisposable = disposableTimeout(() => this.flush(), 500);
}
dispose(): void {
this.disposables.dispose();
this.flushDisposable.dispose();
disposableTimeout(() => this.flush(), 500, this._store);
}
}

View file

@ -41,6 +41,7 @@ export interface IUserDataProfile {
readonly isDefault: boolean;
readonly name: string;
readonly shortName?: string;
readonly icon?: string;
readonly location: URI;
readonly globalStorageHome: URI;
readonly settingsResource: URI;
@ -84,12 +85,14 @@ export type WillRemoveProfileEvent = {
export interface IUserDataProfileOptions {
readonly shortName?: string;
readonly icon?: string;
readonly useDefaultFlags?: UseDefaultProfileFlags;
readonly transient?: boolean;
}
export interface IUserDataProfileUpdateOptions extends IUserDataProfileOptions {
export interface IUserDataProfileUpdateOptions extends Omit<IUserDataProfileOptions, 'icon'> {
readonly name?: string;
readonly icon?: string | null;
}
export const IUserDataProfilesService = createDecorator<IUserDataProfilesService>('IUserDataProfilesService');
@ -124,6 +127,7 @@ export function reviveProfile(profile: UriDto<IUserDataProfile>, scheme: string)
isDefault: profile.isDefault,
name: profile.name,
shortName: profile.shortName,
icon: profile.icon,
location: URI.revive(profile.location).with({ scheme }),
globalStorageHome: URI.revive(profile.globalStorageHome).with({ scheme }),
settingsResource: URI.revive(profile.settingsResource).with({ scheme }),
@ -144,6 +148,7 @@ export function toUserDataProfile(id: string, name: string, location: URI, profi
location,
isDefault: false,
shortName: options?.shortName,
icon: options?.icon,
globalStorageHome: defaultProfile && options?.useDefaultFlags?.globalState ? defaultProfile.globalStorageHome : joinPath(location, 'globalStorage'),
settingsResource: defaultProfile && options?.useDefaultFlags?.settings ? defaultProfile.settingsResource : joinPath(location, 'settings.json'),
keybindingsResource: defaultProfile && options?.useDefaultFlags?.keybindings ? defaultProfile.keybindingsResource : joinPath(location, 'keybindings.json'),
@ -166,6 +171,7 @@ export type StoredUserDataProfile = {
name: string;
location: URI;
shortName?: string;
icon?: string;
useDefaultFlags?: UseDefaultProfileFlags;
};
@ -246,7 +252,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
this.logService.warn('Skipping the invalid stored profile', storedProfile.location || storedProfile.name);
continue;
}
profiles.push(toUserDataProfile(basename(storedProfile.location), storedProfile.name, storedProfile.location, this.profilesCacheHome, { shortName: storedProfile.shortName, useDefaultFlags: storedProfile.useDefaultFlags }, defaultProfile));
profiles.push(toUserDataProfile(basename(storedProfile.location), storedProfile.name, storedProfile.location, this.profilesCacheHome, { shortName: storedProfile.shortName, icon: storedProfile.icon, useDefaultFlags: storedProfile.useDefaultFlags }, defaultProfile));
}
} catch (error) {
this.logService.error(error);
@ -365,7 +371,12 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
throw new Error(`Profile '${profileToUpdate.name}' does not exist`);
}
profile = toUserDataProfile(profile.id, options.name ?? profile.name, profile.location, this.profilesCacheHome, { shortName: options.shortName ?? profile.shortName, transient: options.transient ?? profile.isTransient, useDefaultFlags: options.useDefaultFlags ?? profile.useDefaultFlags }, this.defaultProfile);
profile = toUserDataProfile(profile.id, options.name ?? profile.name, profile.location, this.profilesCacheHome, {
shortName: options.shortName ?? profile.shortName,
icon: options.icon === null ? undefined : options.icon ?? profile.icon,
transient: options.transient ?? profile.isTransient,
useDefaultFlags: options.useDefaultFlags ?? profile.useDefaultFlags
}, this.defaultProfile);
this.updateProfiles([], [], [profile]);
return profile;
@ -516,7 +527,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
if (profile.isTransient) {
this.transientProfilesObject.profiles.push(profile);
} else {
storedProfiles.push({ location: profile.location, name: profile.name, shortName: profile.shortName, useDefaultFlags: profile.useDefaultFlags });
storedProfiles.push({ location: profile.location, name: profile.name, shortName: profile.shortName, icon: profile.icon, useDefaultFlags: profile.useDefaultFlags });
}
}
this.saveStoredProfiles(storedProfiles);

View file

@ -18,6 +18,7 @@ interface IUserDataProfileInfo {
readonly id: string;
readonly name: string;
readonly shortName?: string;
readonly icon?: string;
readonly useDefaultFlags?: UseDefaultProfileFlags;
}
@ -119,7 +120,7 @@ function compare(from: IUserDataProfileInfo[] | null, to: IUserDataProfileInfo[]
const removed = fromKeys.filter(key => !toKeys.includes(key));
const updated: string[] = [];
for (const { id, name, shortName, useDefaultFlags } of from) {
for (const { id, name, shortName, icon, useDefaultFlags } of from) {
if (removed.includes(id)) {
continue;
}
@ -127,6 +128,7 @@ function compare(from: IUserDataProfileInfo[] | null, to: IUserDataProfileInfo[]
if (!toProfile
|| toProfile.name !== name
|| toProfile.shortName !== shortName
|| toProfile.icon !== icon
|| !equals(toProfile.useDefaultFlags, useDefaultFlags)
) {
updated.push(id);

View file

@ -191,7 +191,7 @@ export class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser i
for (const profile of local.added) {
promises.push((async () => {
this.logService.trace(`${this.syncResourceLogLabel}: Creating '${profile.name}' profile...`);
await this.userDataProfilesService.createProfile(profile.id, profile.name, { shortName: profile.shortName, useDefaultFlags: profile.useDefaultFlags });
await this.userDataProfilesService.createProfile(profile.id, profile.name, { shortName: profile.shortName, icon: profile.icon, useDefaultFlags: profile.useDefaultFlags });
this.logService.info(`${this.syncResourceLogLabel}: Created profile '${profile.name}'.`);
})());
}
@ -207,7 +207,7 @@ export class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser i
if (localProfile) {
promises.push((async () => {
this.logService.trace(`${this.syncResourceLogLabel}: Updating '${profile.name}' profile...`);
await this.userDataProfilesService.updateProfile(localProfile, { name: profile.name, shortName: profile.shortName, useDefaultFlags: profile.useDefaultFlags });
await this.userDataProfilesService.updateProfile(localProfile, { name: profile.name, shortName: profile.shortName, icon: profile.icon, useDefaultFlags: profile.useDefaultFlags });
this.logService.info(`${this.syncResourceLogLabel}: Updated profile '${profile.name}'.`);
})());
} else {
@ -225,7 +225,7 @@ export class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser i
for (const profile of remote?.added || []) {
const collection = await this.userDataSyncStoreService.createCollection(this.syncHeaders);
addedCollections.push(collection);
remoteProfiles.push({ id: profile.id, name: profile.name, collection, shortName: profile.shortName, useDefaultFlags: profile.useDefaultFlags });
remoteProfiles.push({ id: profile.id, name: profile.name, collection, shortName: profile.shortName, icon: profile.icon, useDefaultFlags: profile.useDefaultFlags });
}
} else {
this.logService.info(`${this.syncResourceLogLabel}: Could not create remote profiles as there are too many profiles.`);
@ -236,7 +236,7 @@ export class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser i
for (const profile of remote?.updated || []) {
const profileToBeUpdated = remoteProfiles.find(({ id }) => profile.id === id);
if (profileToBeUpdated) {
remoteProfiles.splice(remoteProfiles.indexOf(profileToBeUpdated), 1, { ...profileToBeUpdated, id: profile.id, name: profile.name, shortName: profile.shortName, useDefaultFlags: profile.useDefaultFlags });
remoteProfiles.splice(remoteProfiles.indexOf(profileToBeUpdated), 1, { ...profileToBeUpdated, id: profile.id, name: profile.name, shortName: profile.shortName, icon: profile.icon, useDefaultFlags: profile.useDefaultFlags });
}
}

View file

@ -342,6 +342,7 @@ export interface ISyncUserDataProfile {
readonly collection: string;
readonly name: string;
readonly shortName?: string;
readonly icon?: string;
readonly useDefaultFlags?: UseDefaultProfileFlags;
}

View file

@ -404,7 +404,6 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc
}
paths.push(`syncResource:${syncResourceUriInfo.syncResource}`);
paths.push(`profile:${syncResourceUriInfo.profile}`);
paths.push(syncResourceUriInfo.profile);
if (syncResourceUriInfo.collection) {
paths.push(`collection:${syncResourceUriInfo.collection}`);
}

View file

@ -34,6 +34,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
//#region Editor / Resources DND
@ -95,7 +96,7 @@ export class ResourcesDropHandler {
) {
}
async handleDrop(event: DragEvent, resolveTargetGroup: () => IEditorGroup | undefined, afterDrop: (targetGroup: IEditorGroup | undefined) => void, targetIndex?: number): Promise<void> {
async handleDrop(event: DragEvent, resolveTargetGroup?: () => IEditorGroup | undefined, afterDrop?: (targetGroup: IEditorGroup | undefined) => void, options?: IEditorOptions): Promise<void> {
const editors = await this.instantiationService.invokeFunction(accessor => extractEditorsAndFilesDropData(accessor, event));
if (!editors.length) {
return;
@ -122,19 +123,19 @@ export class ResourcesDropHandler {
}
// Open in Editor
const targetGroup = resolveTargetGroup();
const targetGroup = resolveTargetGroup?.();
await this.editorService.openEditors(editors.map(editor => ({
...editor,
resource: editor.resource,
options: {
...editor.options,
pinned: true,
index: targetIndex
...options,
pinned: true
}
})), targetGroup, { validateTrust: true });
// Finish with provided function
afterDrop(targetGroup);
afterDrop?.(targetGroup);
}
private async handleWorkspaceDrop(resources: URI[]): Promise<boolean> {

View file

@ -492,7 +492,6 @@ export class GlobalActivityActionViewItem extends MenuActivityActionViewItem {
@IKeybindingService keybindingService: IKeybindingService,
) {
super(MenuId.GlobalActivity, action, contextMenuActionsProvider, true, colors, activityHoverOptions, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, environmentService, keybindingService);
this._register(this.userDataProfileService.onDidChangeCurrentProfile(() => this.updateProfileBadge()));
}
override render(container: HTMLElement): void {

View file

@ -43,6 +43,8 @@ import { StringSHA1 } from 'vs/base/common/hash';
import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget';
import { GestureEvent } from 'vs/base/browser/touch';
import { IPaneCompositePart, IPaneCompositeSelectorPart } from 'vs/workbench/browser/parts/paneCompositePart';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
interface IPlaceholderViewContainer {
readonly id: string;
@ -130,6 +132,7 @@ export class ActivitybarPart extends Part implements IPaneCompositeSelectorPart
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService,
) {
super(Parts.ACTIVITYBAR_PART, { hasTitle: false }, themeService, storageService, layoutService);
@ -523,10 +526,11 @@ export class ActivitybarPart extends Part implements IPaneCompositeSelectorPart
preventLoopNavigation: true
}));
this.globalActivityAction = this._register(new ActivityAction({
id: 'workbench.actions.manage',
name: localize('manage', "Manage"),
classNames: ThemeIcon.asClassNameArray(ActivitybarPart.GEAR_ICON),
this.globalActivityAction = this._register(new ActivityAction(this.createGlobalActivity(this.userDataProfileService.currentProfile)));
this._register(this.userDataProfileService.onDidChangeCurrentProfile(e => {
if (this.globalActivityAction) {
this.globalActivityAction.activity = this.createGlobalActivity(e.profile);
}
}));
if (this.accountsVisibilityPreference) {
@ -542,6 +546,14 @@ export class ActivitybarPart extends Part implements IPaneCompositeSelectorPart
this.globalActivityActionBar.push(this.globalActivityAction);
}
private createGlobalActivity(profile: IUserDataProfile): IActivity {
return {
id: 'workbench.actions.manage',
name: localize('manage', "Manage"),
classNames: ThemeIcon.asClassNameArray(profile.icon ? ThemeIcon.fromId(profile.icon) : ActivitybarPart.GEAR_ICON),
};
}
private toggleAccountsActivity() {
if (!!this.accountsActivityAction === this.accountsVisibilityPreference) {
return;

View file

@ -31,6 +31,7 @@ export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = {
tabSizingFixedMinWidth: 50,
tabSizingFixedMaxWidth: 160,
pinnedTabSizing: 'normal',
pinnedTabsOnSeparateRow: false,
tabHeight: 'normal',
preventPinnedEditorClose: 'keyboardAndMouse',
titleScrollbarSizing: 'default',
@ -215,7 +216,7 @@ export interface IInternalEditorCloseOptions extends IInternalEditorTitleControl
context?: EditorCloseContext;
}
export interface IInternalMoveCopyOptions extends IInternalEditorTitleControlOptions {
export interface IInternalMoveCopyOptions extends IInternalEditorOpenOptions {
/**
* Whether to close the editor at the source or keep it.

View file

@ -198,7 +198,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
this.element.appendChild(this.titleContainer);
// Title control
this.titleControl = this._register(this.scopedInstantiationService.createInstance(EditorTitleControl, this.titleContainer, this.accessor, this));
this.titleControl = this._register(this.scopedInstantiationService.createInstance(EditorTitleControl, this.titleContainer, this.accessor, this, this.model));
// Editor container
this.editorContainer = document.createElement('div');
@ -685,8 +685,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Title control
this.titleControl.updateOptions(event.oldPartOptions, event.newPartOptions);
// Title control Switch between showing tabs <=> not showing tabs
if (event.oldPartOptions.showTabs !== event.newPartOptions.showTabs) {
// Title control switch between singleEditorTabs, multiEditorTabs and multiRowEditorTabs
if (
event.oldPartOptions.showTabs !== event.newPartOptions.showTabs ||
(event.oldPartOptions.showTabs && event.oldPartOptions.pinnedTabsOnSeparateRow !== event.newPartOptions.pinnedTabsOnSeparateRow)
) {
// Re-layout
this.relayout();
@ -927,7 +930,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// title control and also make sure to emit this as an event
const newIndexOfEditor = this.getIndexOfEditor(editor);
if (newIndexOfEditor !== oldIndexOfEditor) {
this.titleControl.moveEditor(editor, oldIndexOfEditor, newIndexOfEditor);
this.titleControl.moveEditor(editor, oldIndexOfEditor, newIndexOfEditor, true);
}
// Forward sticky state to title control
@ -979,13 +982,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
supportSideBySide: internalOptions?.supportSideBySide
};
if (options?.sticky && typeof options?.index === 'number' && !this.model.isSticky(options.index)) {
// Special case: we are to open an editor sticky but at an index that is not sticky
// In that case we prefer to open the editor at the index but not sticky. This enables
// to drag a sticky editor to an index that is not sticky to unstick it.
openEditorOptions.sticky = false;
}
if (!openEditorOptions.active && !openEditorOptions.pinned && this.model.activeEditor && !this.model.isPinned(this.model.activeEditor)) {
// Special case: we are to open an editor inactive and not pinned, but the current active
// editor is also not pinned, which means it will get replaced with this one. As such,
@ -1180,7 +1176,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
}
moveEditor(editor: EditorInput, target: EditorGroupView, options?: IEditorOptions, internalOptions?: IInternalEditorTitleControlOptions): void {
moveEditor(editor: EditorInput, target: EditorGroupView, options?: IEditorOptions, internalOptions?: IInternalMoveCopyOptions): void {
// Move within same group
if (this === target) {
@ -1199,26 +1195,35 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return; // do nothing if we move into same group without index
}
const currentIndex = this.model.indexOf(candidate);
if (currentIndex === -1 || currentIndex === moveToIndex) {
return; // do nothing if editor unknown in model or is already at the given index
}
// Update model and make sure to continue to use the editor we get from
// the model. It is possible that the editor was already opened and we
// want to ensure that we use the existing instance in that case.
const currentIndex = this.model.indexOf(candidate);
const editor = this.model.getEditorByIndex(currentIndex);
if (!editor) {
return;
}
// Update model
this.model.moveEditor(editor, moveToIndex);
this.model.pin(editor);
// Move when index has actually changed
if (currentIndex !== moveToIndex) {
const oldStickyCount = this.model.stickyCount;
// Forward to title control
this.titleControl.moveEditor(editor, currentIndex, moveToIndex);
this.titleControl.pinEditor(editor);
// Update model
this.model.moveEditor(editor, moveToIndex);
this.model.pin(editor);
// Forward to title control
this.titleControl.moveEditor(editor, currentIndex, moveToIndex, oldStickyCount !== this.model.stickyCount);
this.titleControl.pinEditor(editor);
}
// Support the option to stick the editor even if it is moved.
// It is important that we call this method after we have moved
// the editor because the result of moving the editor could have
// caused a change in sticky state.
if (options?.sticky) {
this.stickEditor(editor);
}
}
private doMoveOrCopyEditorAcrossGroups(editor: EditorInput, target: EditorGroupView, openOptions?: IEditorOpenOptions, internalOptions?: IInternalMoveCopyOptions): void {
@ -1229,8 +1234,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// if so
const options = fillActiveEditorViewState(this, editor, {
...openOptions,
pinned: true, // always pin moved editor
sticky: !keepCopy && this.model.isSticky(editor) // preserve sticky state only if editor is moved (https://github.com/microsoft/vscode/issues/99035)
pinned: true, // always pin moved editor
sticky: openOptions?.sticky ?? (!keepCopy && this.model.isSticky(editor)) // preserve sticky state only if editor is moved or eplicitly wanted (https://github.com/microsoft/vscode/issues/99035)
});
// Indicate will move event

View file

@ -11,7 +11,7 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { ActionsOrientation, IActionViewItem, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar';
import { IAction, SubmenuAction, ActionRunner } from 'vs/base/common/actions';
import { ResolvedKeybinding } from 'vs/base/common/keybindings';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { createActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
@ -38,6 +38,7 @@ import { LocalSelectionTransfer } from 'vs/platform/dnd/browser/dnd';
import { DraggedTreeItemsIdentifier } from 'vs/editor/common/services/treeViewsDnd';
import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService';
import { IEditorTitleControlDimensions } from 'vs/workbench/browser/parts/editor/editorTitleControl';
import { IReadonlyEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel';
export interface IToolbarActions {
readonly primary: IAction[];
@ -70,7 +71,25 @@ export class EditorCommandsContextActionRunner extends ActionRunner {
}
}
export abstract class EditorTabsControl extends Themable {
export interface IEditorTabsControl extends IDisposable {
updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void;
openEditor(editor: EditorInput): boolean;
openEditors(editors: EditorInput[]): boolean;
beforeCloseEditor(editor: EditorInput): void;
closeEditor(editor: EditorInput): void;
closeEditors(editors: EditorInput[]): void;
moveEditor(editor: EditorInput, fromIndex: number, targetIndex: number, stickyStateChange: boolean): void;
pinEditor(editor: EditorInput): void;
stickEditor(editor: EditorInput): void;
unstickEditor(editor: EditorInput): void;
setActive(isActive: boolean): void;
updateEditorLabel(editor: EditorInput): void;
updateEditorDirty(editor: EditorInput): void;
layout(dimensions: IEditorTitleControlDimensions): Dimension;
getHeight(): number;
}
export abstract class EditorTabsControl extends Themable implements IEditorTabsControl {
protected readonly editorTransfer = LocalSelectionTransfer.getInstance<DraggedEditorIdentifier>();
protected readonly groupTransfer = LocalSelectionTransfer.getInstance<DraggedEditorGroupIdentifier>();
@ -103,7 +122,8 @@ export abstract class EditorTabsControl extends Themable {
constructor(
private parent: HTMLElement,
protected accessor: IEditorGroupsAccessor,
protected group: IEditorGroupView,
protected groupViewer: IEditorGroupView,
protected tabsModel: IReadonlyEditorGroupModel,
@IContextMenuService protected readonly contextMenuService: IContextMenuService,
@IInstantiationService protected instantiationService: IInstantiationService,
@IContextKeyService protected readonly contextKeyService: IContextKeyService,
@ -139,7 +159,7 @@ export abstract class EditorTabsControl extends Themable {
}
protected createEditorActionsToolBar(container: HTMLElement): void {
const context: IEditorCommandsContext = { groupId: this.group.id };
const context: IEditorCommandsContext = { groupId: this.groupViewer.id };
// Toolbar Widget
@ -171,7 +191,7 @@ export abstract class EditorTabsControl extends Themable {
}
private actionViewItemProvider(action: IAction): IActionViewItem | undefined {
const activeEditorPane = this.group.activeEditorPane;
const activeEditorPane = this.groupViewer.activeEditorPane;
// Check Active Editor
if (activeEditorPane instanceof EditorPane) {
@ -204,24 +224,24 @@ export abstract class EditorTabsControl extends Themable {
// Update contexts
this.contextKeyService.bufferChangeEvents(() => {
const activeEditor = this.group.activeEditor;
const activeEditor = this.groupViewer.activeEditor;
this.resourceContext.set(EditorResourceAccessor.getOriginalUri(activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY } ?? null));
this.editorPinnedContext.set(activeEditor ? this.group.isPinned(activeEditor) : false);
this.editorIsFirstContext.set(activeEditor ? this.group.isFirst(activeEditor) : false);
this.editorIsLastContext.set(activeEditor ? this.group.isLast(activeEditor) : false);
this.editorStickyContext.set(activeEditor ? this.group.isSticky(activeEditor) : false);
this.editorPinnedContext.set(activeEditor ? this.groupViewer.isPinned(activeEditor) : false);
this.editorIsFirstContext.set(activeEditor ? this.groupViewer.isFirst(activeEditor) : false);
this.editorIsLastContext.set(activeEditor ? this.groupViewer.isLast(activeEditor) : false);
this.editorStickyContext.set(activeEditor ? this.groupViewer.isSticky(activeEditor) : false);
applyAvailableEditorIds(this.editorAvailableEditorIds, activeEditor, this.editorResolverService);
this.editorCanSplitInGroupContext.set(activeEditor ? activeEditor.hasCapability(EditorInputCapabilities.CanSplitInGroup) : false);
this.sideBySideEditorContext.set(activeEditor?.typeId === SideBySideEditorInput.ID);
this.groupLockedContext.set(this.group.isLocked);
this.groupLockedContext.set(this.groupViewer.isLocked);
});
// Editor actions require the editor control to be there, so we retrieve it via service
const activeEditorPane = this.group.activeEditorPane;
const activeEditorPane = this.groupViewer.activeEditorPane;
if (activeEditorPane instanceof EditorPane) {
const scopedContextKeyService = this.getEditorPaneAwareContextKeyService();
const titleBarMenu = this.menuService.createMenu(MenuId.EditorTitle, scopedContextKeyService, { emitEventsForSubmenuChanges: true, eventDebounceDelay: 0 });
@ -245,7 +265,7 @@ export abstract class EditorTabsControl extends Themable {
}
private getEditorPaneAwareContextKeyService(): IContextKeyService {
return this.group.activeEditorPane?.scopedContextKeyService ?? this.contextKeyService;
return this.groupViewer.activeEditorPane?.scopedContextKeyService ?? this.contextKeyService;
}
protected clearEditorActionsToolbar(): void {
@ -261,7 +281,7 @@ export abstract class EditorTabsControl extends Themable {
}
// Set editor group as transfer
this.groupTransfer.setData([new DraggedEditorGroupIdentifier(this.group.id)], DraggedEditorGroupIdentifier.prototype);
this.groupTransfer.setData([new DraggedEditorGroupIdentifier(this.groupViewer.id)], DraggedEditorGroupIdentifier.prototype);
if (e.dataTransfer) {
e.dataTransfer.effectAllowed = 'copyMove';
}
@ -269,26 +289,26 @@ export abstract class EditorTabsControl extends Themable {
// Drag all tabs of the group if tabs are enabled
let hasDataTransfer = false;
if (this.accessor.partOptions.showTabs) {
hasDataTransfer = this.doFillResourceDataTransfers(this.group.getEditors(EditorsOrder.SEQUENTIAL), e);
hasDataTransfer = this.doFillResourceDataTransfers(this.groupViewer.getEditors(EditorsOrder.SEQUENTIAL), e);
}
// Otherwise only drag the active editor
else {
if (this.group.activeEditor) {
hasDataTransfer = this.doFillResourceDataTransfers([this.group.activeEditor], e);
if (this.groupViewer.activeEditor) {
hasDataTransfer = this.doFillResourceDataTransfers([this.groupViewer.activeEditor], e);
}
}
// Firefox: requires to set a text data transfer to get going
if (!hasDataTransfer && isFirefox) {
e.dataTransfer?.setData(DataTransfers.TEXT, String(this.group.label));
e.dataTransfer?.setData(DataTransfers.TEXT, String(this.groupViewer.label));
}
// Drag Image
if (this.group.activeEditor) {
let label = this.group.activeEditor.getName();
if (this.accessor.partOptions.showTabs && this.group.count > 1) {
label = localize('draggedEditorGroup', "{0} (+{1})", label, this.group.count - 1);
if (this.groupViewer.activeEditor) {
let label = this.groupViewer.activeEditor.getName();
if (this.accessor.partOptions.showTabs && this.groupViewer.count > 1) {
label = localize('draggedEditorGroup', "{0} (+{1})", label, this.groupViewer.count - 1);
}
applyDragImage(e, label, 'monaco-editor-group-drag-image', this.getColor(listActiveSelectionBackground), this.getColor(listActiveSelectionForeground));
@ -303,7 +323,7 @@ export abstract class EditorTabsControl extends Themable {
protected doFillResourceDataTransfers(editors: readonly EditorInput[], e: DragEvent): boolean {
if (editors.length) {
this.instantiationService.invokeFunction(fillEditorsDragData, editors.map(editor => ({ editor, groupId: this.group.id })), e);
this.instantiationService.invokeFunction(fillEditorsDragData, editors.map(editor => ({ editor, groupId: this.groupViewer.id })), e);
return true;
}
@ -311,21 +331,21 @@ export abstract class EditorTabsControl extends Themable {
return false;
}
protected onContextMenu(editor: EditorInput, e: Event, node: HTMLElement): void {
protected onTabContextMenu(editor: EditorInput, e: Event, node: HTMLElement): void {
// Update contexts based on editor picked and remember previous to restore
const currentResourceContext = this.resourceContext.get();
this.resourceContext.set(EditorResourceAccessor.getOriginalUri(editor, { supportSideBySide: SideBySideEditor.PRIMARY } ?? null));
const currentPinnedContext = !!this.editorPinnedContext.get();
this.editorPinnedContext.set(this.group.isPinned(editor));
this.editorPinnedContext.set(this.tabsModel.isPinned(editor));
const currentEditorIsFirstContext = !!this.editorIsFirstContext.get();
this.editorIsFirstContext.set(this.group.isFirst(editor));
this.editorIsFirstContext.set(this.tabsModel.isFirst(editor));
const currentEditorIsLastContext = !!this.editorIsLastContext.get();
this.editorIsLastContext.set(this.group.isLast(editor));
this.editorIsLastContext.set(this.tabsModel.isLast(editor));
const currentStickyContext = !!this.editorStickyContext.get();
this.editorStickyContext.set(this.group.isSticky(editor));
this.editorStickyContext.set(this.tabsModel.isSticky(editor));
const currentGroupLockedContext = !!this.groupLockedContext.get();
this.groupLockedContext.set(this.group.isLocked);
this.groupLockedContext.set(this.tabsModel.isLocked);
const currentEditorCanSplitContext = !!this.editorCanSplitInGroupContext.get();
this.editorCanSplitInGroupContext.set(editor.hasCapability(EditorInputCapabilities.CanSplitInGroup));
const currentSideBySideEditorContext = !!this.sideBySideEditorContext.get();
@ -345,7 +365,7 @@ export abstract class EditorTabsControl extends Themable {
menuId: MenuId.EditorTitleContext,
menuActionOptions: { shouldForwardArgs: true, arg: this.resourceContext.get() },
contextKeyService: this.contextKeyService,
getActionsContext: () => ({ groupId: this.group.id, editorIndex: this.group.getIndexOfEditor(editor) }),
getActionsContext: () => ({ groupId: this.groupViewer.id, editorIndex: this.groupViewer.getIndexOfEditor(editor) }),
getKeyBinding: action => this.getKeybinding(action),
onHide: () => {

View file

@ -9,12 +9,14 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IThemeService, Themable } from 'vs/platform/theme/common/themeService';
import { BreadcrumbsControl, BreadcrumbsControlFactory } from 'vs/workbench/browser/parts/editor/breadcrumbsControl';
import { IEditorGroupsAccessor, IEditorGroupTitleHeight, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
import { EditorTabsControl } from 'vs/workbench/browser/parts/editor/editorTabsControl';
import { IEditorTabsControl } from 'vs/workbench/browser/parts/editor/editorTabsControl';
import { MultiEditorTabsControl } from 'vs/workbench/browser/parts/editor/multiEditorTabsControl';
import { SingleEditorTabsControl } from 'vs/workbench/browser/parts/editor/singleEditorTabsControl';
import { IEditorPartOptions } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { MultiRowEditorControl } from 'vs/workbench/browser/parts/editor/multiRowEditorTabsControl';
import { IReadonlyEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel';
export interface IEditorTitleControlDimensions {
@ -32,7 +34,7 @@ export interface IEditorTitleControlDimensions {
export class EditorTitleControl extends Themable {
private editorTabsControl: EditorTabsControl;
private editorTabsControl: IEditorTabsControl;
private editorTabsControlDisposable = this._register(new DisposableStore());
private breadcrumbsControlFactory: BreadcrumbsControlFactory | undefined;
@ -42,7 +44,8 @@ export class EditorTitleControl extends Themable {
constructor(
private parent: HTMLElement,
private accessor: IEditorGroupsAccessor,
private group: IEditorGroupView,
private groupViewer: IEditorGroupView,
private model: IReadonlyEditorGroupModel,
@IInstantiationService private instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService
) {
@ -52,12 +55,16 @@ export class EditorTitleControl extends Themable {
this.breadcrumbsControlFactory = this.createBreadcrumbsControl();
}
private createEditorTabsControl(): EditorTabsControl {
let control: EditorTabsControl;
private createEditorTabsControl(): IEditorTabsControl {
let control: IEditorTabsControl;
if (this.accessor.partOptions.showTabs) {
control = this.instantiationService.createInstance(MultiEditorTabsControl, this.parent, this.accessor, this.group);
if (this.accessor.partOptions.pinnedTabsOnSeparateRow) {
control = this.instantiationService.createInstance(MultiRowEditorControl, this.parent, this.accessor, this.groupViewer, this.model);
} else {
control = this.instantiationService.createInstance(MultiEditorTabsControl, this.parent, this.accessor, this.groupViewer, this.model);
}
} else {
control = this.instantiationService.createInstance(SingleEditorTabsControl, this.parent, this.accessor, this.group);
control = this.instantiationService.createInstance(SingleEditorTabsControl, this.parent, this.accessor, this.groupViewer, this.model);
}
return this.editorTabsControlDisposable.add(control);
@ -73,7 +80,7 @@ export class EditorTitleControl extends Themable {
breadcrumbsContainer.classList.add('breadcrumbs-below-tabs');
this.parent.appendChild(breadcrumbsContainer);
const breadcrumbsControlFactory = this.breadcrumbsControlDisposables.add(this.instantiationService.createInstance(BreadcrumbsControlFactory, breadcrumbsContainer, this.group, {
const breadcrumbsControlFactory = this.breadcrumbsControlDisposables.add(this.instantiationService.createInstance(BreadcrumbsControlFactory, breadcrumbsContainer, this.groupViewer, {
showFileIcons: true,
showSymbolIcons: true,
showDecorationColors: false,
@ -85,7 +92,7 @@ export class EditorTitleControl extends Themable {
}
private handleBreadcrumbsEnablementChange(): void {
this.group.relayout(); // relayout when breadcrumbs are enable/disabled
this.groupViewer.relayout(); // relayout when breadcrumbs are enable/disabled
}
openEditor(editor: EditorInput): void {
@ -125,13 +132,13 @@ export class EditorTitleControl extends Themable {
}
private handleClosedEditors(): void {
if (!this.group.activeEditor) {
if (!this.groupViewer.activeEditor) {
this.breadcrumbsControl?.update();
}
}
moveEditor(editor: EditorInput, fromIndex: number, targetIndex: number): void {
return this.editorTabsControl.moveEditor(editor, fromIndex, targetIndex);
moveEditor(editor: EditorInput, fromIndex: number, targetIndex: number, stickyStateChange: boolean): void {
return this.editorTabsControl.moveEditor(editor, fromIndex, targetIndex, stickyStateChange);
}
pinEditor(editor: EditorInput): void {
@ -159,10 +166,11 @@ export class EditorTitleControl extends Themable {
}
updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void {
// Update editor tabs control if options changed
if (oldOptions.showTabs !== newOptions.showTabs) {
if (
oldOptions.showTabs !== newOptions.showTabs ||
(newOptions.showTabs && oldOptions.pinnedTabsOnSeparateRow !== newOptions.pinnedTabsOnSeparateRow)
) {
// Clear old
this.editorTabsControlDisposable.clear();
this.breadcrumbsControlDisposables.clear();
@ -174,7 +182,9 @@ export class EditorTitleControl extends Themable {
}
// Forward into editor tabs control
this.editorTabsControl.updateOptions(oldOptions, newOptions);
else {
this.editorTabsControl.updateOptions(oldOptions, newOptions);
}
}
layout(dimensions: IEditorTitleControlDimensions): Dimension {

View file

@ -39,13 +39,17 @@
##########################################################################################
*/
/* Title Container */
/* Tabs and Actions Container */
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container {
display: flex;
position: relative; /* position tabs border bottom or editor actions (when tabs wrap) relative to this container */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.empty {
display: none;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.tabs-border-bottom::after {
content: '';
position: absolute;
@ -444,3 +448,9 @@
bottom: 0;
right: 0;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title.two-tab-bars > .tabs-and-actions-container:first-child .editor-actions {
/* When multiple tab bars are visible, only show editor actions for the last tab bar */
display: none;
}

View file

@ -6,7 +6,7 @@
import 'vs/css!./media/multieditortabscontrol';
import { isMacintosh, isWindows } from 'vs/base/common/platform';
import { shorten } from 'vs/base/common/labels';
import { EditorResourceAccessor, GroupIdentifier, Verbosity, IEditorPartOptions, SideBySideEditor, DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, IUntypedEditorInput, preventEditorClose, EditorCloseMethod } from 'vs/workbench/common/editor';
import { EditorResourceAccessor, GroupIdentifier, Verbosity, IEditorPartOptions, SideBySideEditor, DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, IUntypedEditorInput, preventEditorClose, EditorCloseMethod, EditorsOrder } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { computeEditorAriaLabel } from 'vs/workbench/browser/editor';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
@ -34,7 +34,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import { MergeGroupMode, IMergeGroupOptions, GroupsArrangement, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { addDisposableListener, EventType, EventHelper, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode, DragAndDropObserver } from 'vs/base/browser/dom';
import { localize } from 'vs/nls';
import { IEditorGroupsAccessor, IEditorGroupView, EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
import { IEditorGroupsAccessor, EditorServiceImpl, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
import { CloseOneEditorAction, UnpinEditorAction } from 'vs/workbench/browser/parts/editor/editorActions';
import { assertAllDefined, assertIsDefined } from 'vs/base/common/types';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@ -46,13 +46,15 @@ import { coalesce, insert } from 'vs/base/common/arrays';
import { isHighContrast } from 'vs/platform/theme/common/theme';
import { isSafari } from 'vs/base/browser/browser';
import { equals } from 'vs/base/common/objects';
import { EditorActivation } from 'vs/platform/editor/common/editor';
import { EditorActivation, IEditorOptions } from 'vs/platform/editor/common/editor';
import { UNLOCK_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { ITreeViewsDnDService } from 'vs/editor/common/services/treeViewsDndService';
import { DraggedTreeItemsIdentifier } from 'vs/editor/common/services/treeViewsDnd';
import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService';
import { IEditorTitleControlDimensions } from 'vs/workbench/browser/parts/editor/editorTitleControl';
import { StickyEditorGroupModel, UnstickyEditorGroupModel } from 'vs/workbench/common/editor/filteredEditorGroupModel';
import { IReadonlyEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel';
interface IEditorInputLabel {
readonly editor: EditorInput;
@ -133,7 +135,8 @@ export class MultiEditorTabsControl extends EditorTabsControl {
constructor(
parent: HTMLElement,
accessor: IEditorGroupsAccessor,
group: IEditorGroupView,
groupViewer: IEditorGroupView,
tabsModel: IReadonlyEditorGroupModel,
@IContextMenuService contextMenuService: IContextMenuService,
@IInstantiationService instantiationService: IInstantiationService,
@IContextKeyService contextKeyService: IContextKeyService,
@ -148,7 +151,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
@ITreeViewsDnDService private readonly treeViewsDragAndDropService: ITreeViewsDnDService,
@IEditorResolverService editorResolverService: IEditorResolverService
) {
super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, menuService, quickInputService, themeService, editorResolverService);
super(parent, accessor, groupViewer, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, menuService, quickInputService, themeService, editorResolverService);
// Resolve the correct path library for the OS we are on
// If we are connected to remote, this accounts for the
@ -193,6 +196,9 @@ export class MultiEditorTabsControl extends EditorTabsControl {
// Editor Actions Toolbar
this.createEditorActionsToolBar(this.editorToolbarContainer);
// Set tabs control visibility
this.updateTabsControlVisibility();
}
private createTabsScrollbar(scrollable: HTMLElement): ScrollableElement {
@ -249,7 +255,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
}
private updateTabsFixedWidth(fixed: boolean): void {
this.forEachTab((editor, index, tabContainer) => {
this.forEachTab((editor, tabIndex, tabContainer) => {
if (fixed) {
const { width } = tabContainer.getBoundingClientRect();
tabContainer.style.setProperty('--tab-sizing-current-width', `${width}px`);
@ -304,10 +310,10 @@ export class MultiEditorTabsControl extends EditorTabsControl {
resource: undefined,
options: {
pinned: true,
index: this.group.count, // always at the end
index: this.groupViewer.count, // always at the end
override: DEFAULT_EDITOR_ASSOCIATION.id
}
}, this.group.id);
}, this.groupViewer.id);
}));
}
@ -348,7 +354,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype);
if (Array.isArray(data)) {
const localDraggedEditor = data[0].identifier;
if (this.group.id === localDraggedEditor.groupId && this.group.getIndexOfEditor(localDraggedEditor.editor) === this.group.count - 1) {
if (this.groupViewer.id === localDraggedEditor.groupId && this.tabsModel.isLast(localDraggedEditor.editor)) {
if (e.dataTransfer) {
e.dataTransfer.dropEffect = 'none';
}
@ -384,15 +390,15 @@ export class MultiEditorTabsControl extends EditorTabsControl {
tabsContainer.classList.remove('scroll');
if (e.target === tabsContainer) {
this.onDrop(e, this.group.count, tabsContainer);
this.onDrop(e, this.tabsModel.count, tabsContainer);
}
}
}));
// Mouse-wheel support to switch to tabs optionally
this._register(addDisposableListener(tabsContainer, EventType.MOUSE_WHEEL, (e: WheelEvent) => {
const activeEditor = this.group.activeEditor;
if (!activeEditor || this.group.count < 2) {
const activeEditor = this.groupViewer.activeEditor;
if (!activeEditor || this.groupViewer.count < 2) {
return; // need at least 2 open editors
}
@ -427,13 +433,13 @@ export class MultiEditorTabsControl extends EditorTabsControl {
return;
}
const nextEditor = this.group.getEditorByIndex(this.group.getIndexOfEditor(activeEditor) + tabSwitchDirection);
const nextEditor = this.groupViewer.getEditorByIndex(this.groupViewer.getIndexOfEditor(activeEditor) + tabSwitchDirection);
if (!nextEditor) {
return;
}
// Open it
this.group.openEditor(nextEditor);
this.groupViewer.openEditor(nextEditor);
// Disable normal scrolling, opening the editor will already reveal it properly
EventHelper.stop(e, true);
@ -455,9 +461,9 @@ export class MultiEditorTabsControl extends EditorTabsControl {
menuId: MenuId.EditorTabsBarContext,
contextKeyService: this.contextKeyService,
menuActionOptions: { shouldForwardArgs: true },
getActionsContext: () => ({ groupId: this.group.id }),
getActionsContext: () => ({ groupId: this.groupViewer.id }),
getKeyBinding: action => this.getKeybinding(action),
onHide: () => this.group.focus()
onHide: () => this.groupViewer.focus()
});
};
@ -490,9 +496,12 @@ export class MultiEditorTabsControl extends EditorTabsControl {
private handleOpenedEditors(): boolean {
// Set tabs control visibility
this.updateTabsControlVisibility();
// Create tabs as needed
const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar);
for (let i = tabsContainer.children.length; i < this.group.count; i++) {
for (let i = tabsContainer.children.length; i < this.tabsModel.count; i++) {
tabsContainer.appendChild(this.createTab(i, tabsContainer, tabsScrollbar));
}
@ -526,9 +535,9 @@ export class MultiEditorTabsControl extends EditorTabsControl {
private didActiveEditorChange(): boolean {
if (
!this.activeTabLabel?.editor && this.group.activeEditor || // active editor changed from null => editor
this.activeTabLabel?.editor && !this.group.activeEditor || // active editor changed from editor => null
(!this.activeTabLabel?.editor || !this.group.isActive(this.activeTabLabel.editor)) // active editor changed from editorA => editorB
!this.activeTabLabel?.editor && this.tabsModel.activeEditor || // active editor changed from null => editor
this.activeTabLabel?.editor && !this.tabsModel.activeEditor || // active editor changed from editor => null
(!this.activeTabLabel?.editor || !this.tabsModel.isActive(this.activeTabLabel.editor)) // active editor changed from editorA => editorB
) {
return true;
}
@ -560,7 +569,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
// the mouse and allows for rapid closing of tabs.
if (this.isMouseOverTabs && this.accessor.partOptions.tabSizing === 'fixed') {
const closingLastTab = this.group.isLast(editor);
const closingLastTab = this.tabsModel.isLast(editor);
this.updateTabsFixedWidth(!closingLastTab);
}
}
@ -576,11 +585,11 @@ export class MultiEditorTabsControl extends EditorTabsControl {
private handleClosedEditors(): void {
// There are tabs to show
if (this.group.activeEditor) {
if (this.tabsModel.count) {
// Remove tabs that got closed
const tabsContainer = assertIsDefined(this.tabsContainer);
while (tabsContainer.children.length > this.group.count) {
while (tabsContainer.children.length > this.tabsModel.count) {
// Remove one tab from container (must be the last to keep indexes in order!)
tabsContainer.lastChild?.remove();
@ -609,22 +618,23 @@ export class MultiEditorTabsControl extends EditorTabsControl {
this.tabActionBars = [];
this.clearEditorActionsToolbar();
this.updateTabsControlVisibility();
}
}
moveEditor(editor: EditorInput, fromIndex: number, targetIndex: number): void {
moveEditor(editor: EditorInput, fromTabIndex: number, targeTabIndex: number): void {
// Move the editor label
const editorLabel = this.tabLabels[fromIndex];
this.tabLabels.splice(fromIndex, 1);
this.tabLabels.splice(targetIndex, 0, editorLabel);
const editorLabel = this.tabLabels[fromTabIndex];
this.tabLabels.splice(fromTabIndex, 1);
this.tabLabels.splice(targeTabIndex, 0, editorLabel);
// Redraw tabs in the range of the move
this.forEachTab((editor, index, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => {
this.redrawTab(editor, index, tabContainer, tabLabelWidget, tabLabel, tabActionBar);
this.forEachTab((editor, tabIndex, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => {
this.redrawTab(editor, tabIndex, tabContainer, tabLabelWidget, tabLabel, tabActionBar);
},
Math.min(fromIndex, targetIndex), // from: smallest of fromIndex/targetIndex
Math.max(fromIndex, targetIndex) // to: largest of fromIndex/targetIndex
Math.min(fromTabIndex, targeTabIndex), // from: smallest of fromTabIndex/targeTabIndex
Math.max(fromTabIndex, targeTabIndex) // to: largest of fromTabIndex/targeTabIndex
);
// Moving an editor requires a layout to keep the active editor visible
@ -632,7 +642,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
}
pinEditor(editor: EditorInput): void {
this.withTab(editor, (editor, index, tabContainer, tabLabelWidget, tabLabel) => this.redrawTabLabel(editor, index, tabContainer, tabLabelWidget, tabLabel));
this.withTab(editor, (editor, tabIndex, tabContainer, tabLabelWidget, tabLabel) => this.redrawTabLabel(editor, tabIndex, tabContainer, tabLabelWidget, tabLabel));
}
stickEditor(editor: EditorInput): void {
@ -646,12 +656,12 @@ export class MultiEditorTabsControl extends EditorTabsControl {
private doHandleStickyEditorChange(editor: EditorInput): void {
// Update tab
this.withTab(editor, (editor, index, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => this.redrawTab(editor, index, tabContainer, tabLabelWidget, tabLabel, tabActionBar));
this.withTab(editor, (editor, tabIndex, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => this.redrawTab(editor, tabIndex, tabContainer, tabLabelWidget, tabLabel, tabActionBar));
// Sticky change has an impact on each tab's border because
// it potentially moves the border to the last pinned tab
this.forEachTab((editor, index, tabContainer, tabLabelWidget, tabLabel) => {
this.redrawTabBorders(index, tabContainer);
this.forEachTab((editor, tabIndex, tabContainer, tabLabelWidget, tabLabel) => {
this.redrawTabBorders(tabIndex, tabContainer);
});
// A change to the sticky state requires a layout to keep the active editor visible
@ -661,7 +671,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
setActive(isGroupActive: boolean): void {
// Activity has an impact on each tab's active indication
this.forEachTab((editor, index, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => {
this.forEachTab((editor, tabIndex, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => {
this.redrawTabActiveAndDirty(isGroupActive, editor, tabContainer, tabActionBar);
});
@ -688,8 +698,8 @@ export class MultiEditorTabsControl extends EditorTabsControl {
this.computeTabLabels();
// As such we need to redraw each label
this.forEachTab((editor, index, tabContainer, tabLabelWidget, tabLabel) => {
this.redrawTabLabel(editor, index, tabContainer, tabLabelWidget, tabLabel);
this.forEachTab((editor, tabIndex, tabContainer, tabLabelWidget, tabLabel) => {
this.redrawTabLabel(editor, tabIndex, tabContainer, tabLabelWidget, tabLabel);
});
// A change to a label requires a layout to keep the active editor visible
@ -697,7 +707,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
}
updateEditorDirty(editor: EditorInput): void {
this.withTab(editor, (editor, index, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => this.redrawTabActiveAndDirty(this.accessor.activeGroup === this.group, editor, tabContainer, tabActionBar));
this.withTab(editor, (editor, tabIndex, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => this.redrawTabActiveAndDirty(this.accessor.activeGroup === this.groupViewer, editor, tabContainer, tabActionBar));
}
override updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void {
@ -742,36 +752,36 @@ export class MultiEditorTabsControl extends EditorTabsControl {
this.redraw();
}
private forEachTab(fn: (editor: EditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar) => void, fromIndex?: number, toIndex?: number): void {
this.group.editors.forEach((editor, index) => {
if (typeof fromIndex === 'number' && fromIndex > index) {
private forEachTab(fn: (editor: EditorInput, tabIndex: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar) => void, fromTabIndex?: number, toTabIndex?: number): void {
this.tabsModel.getEditors(EditorsOrder.SEQUENTIAL).forEach((editor: EditorInput, tabIndex: number) => {
if (typeof fromTabIndex === 'number' && fromTabIndex > tabIndex) {
return; // do nothing if we are not yet at `fromIndex`
}
if (typeof toIndex === 'number' && toIndex < index) {
if (typeof toTabIndex === 'number' && toTabIndex < tabIndex) {
return; // do nothing if we are beyond `toIndex`
}
this.doWithTab(index, editor, fn);
this.doWithTab(tabIndex, editor, fn);
});
}
private withTab(editor: EditorInput, fn: (editor: EditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar) => void): void {
this.doWithTab(this.group.getIndexOfEditor(editor), editor, fn);
private withTab(editor: EditorInput, fn: (editor: EditorInput, tabIndex: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar) => void): void {
this.doWithTab(this.tabsModel.indexOf(editor), editor, fn);
}
private doWithTab(index: number, editor: EditorInput, fn: (editor: EditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar) => void): void {
private doWithTab(tabIndex: number, editor: EditorInput, fn: (editor: EditorInput, tabIndex: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar) => void): void {
const tabsContainer = assertIsDefined(this.tabsContainer);
const tabContainer = tabsContainer.children[index] as HTMLElement;
const tabResourceLabel = this.tabResourceLabels.get(index);
const tabLabel = this.tabLabels[index];
const tabActionBar = this.tabActionBars[index];
const tabContainer = tabsContainer.children[tabIndex] as HTMLElement;
const tabResourceLabel = this.tabResourceLabels.get(tabIndex);
const tabLabel = this.tabLabels[tabIndex];
const tabActionBar = this.tabActionBars[tabIndex];
if (tabContainer && tabResourceLabel && tabLabel) {
fn(editor, index, tabContainer, tabResourceLabel, tabLabel, tabActionBar);
fn(editor, tabIndex, tabContainer, tabResourceLabel, tabLabel, tabActionBar);
}
}
private createTab(index: number, tabsContainer: HTMLElement, tabsScrollbar: ScrollableElement): HTMLElement {
private createTab(tabIndex: number, tabsContainer: HTMLElement, tabsScrollbar: ScrollableElement): HTMLElement {
// Tab Container
const tabContainer = document.createElement('div');
@ -795,7 +805,15 @@ export class MultiEditorTabsControl extends EditorTabsControl {
tabActionsContainer.classList.add('tab-actions');
tabContainer.appendChild(tabActionsContainer);
const tabActionRunner = new EditorCommandsContextActionRunner({ groupId: this.group.id, editorIndex: index });
const that = this;
const tabActionRunner = new EditorCommandsContextActionRunner({
groupId: this.groupViewer.id,
get editorIndex() {
const editor = assertIsDefined(that.tabsModel.getEditorByIndex(tabIndex));
return that.groupViewer.getIndexOfEditor(editor);
},
});
const tabActionBar = new ActionBar(tabActionsContainer, { ariaLabel: localize('ariaLabelTabActions', "Tab actions"), actionRunner: tabActionRunner });
const tabActionListener = tabActionBar.onWillRun(e => {
@ -812,14 +830,14 @@ export class MultiEditorTabsControl extends EditorTabsControl {
tabContainer.appendChild(tabBorderBottomContainer);
// Eventing
const eventsDisposable = this.registerTabListeners(tabContainer, index, tabsContainer, tabsScrollbar);
const eventsDisposable = this.registerTabListeners(tabContainer, tabIndex, tabsContainer, tabsScrollbar);
this.tabDisposables.push(combinedDisposable(eventsDisposable, tabActionBarDisposable, tabActionRunner, editorLabel));
return tabContainer;
}
private registerTabListeners(tab: HTMLElement, index: number, tabsContainer: HTMLElement, tabsScrollbar: ScrollableElement): IDisposable {
private registerTabListeners(tab: HTMLElement, tabIndex: number, tabsContainer: HTMLElement, tabsScrollbar: ScrollableElement): IDisposable {
const disposables = new DisposableStore();
const handleClickOrTouch = (e: MouseEvent | GestureEvent, preserveFocus: boolean): void => {
@ -838,10 +856,10 @@ export class MultiEditorTabsControl extends EditorTabsControl {
}
// Open tabs editor
const editor = this.group.getEditorByIndex(index);
const editor = this.tabsModel.getEditorByIndex(tabIndex);
if (editor) {
// Even if focus is preserved make sure to activate the group.
this.group.openEditor(editor, { preserveFocus, activation: EditorActivation.ACTIVATE });
this.groupViewer.openEditor(editor, { preserveFocus, activation: EditorActivation.ACTIVATE });
}
return undefined;
@ -850,9 +868,9 @@ export class MultiEditorTabsControl extends EditorTabsControl {
const showContextMenu = (e: Event) => {
EventHelper.stop(e);
const editor = this.group.getEditorByIndex(index);
const editor = this.tabsModel.getEditorByIndex(tabIndex);
if (editor) {
this.onContextMenu(editor, e, tab);
this.onTabContextMenu(editor, e, tab);
}
};
@ -877,13 +895,15 @@ export class MultiEditorTabsControl extends EditorTabsControl {
if (e.button === 1 /* Middle Button*/) {
EventHelper.stop(e, true /* for https://github.com/microsoft/vscode/issues/56715 */);
const editor = this.group.getEditorByIndex(index);
if (editor && preventEditorClose(this.group, editor, EditorCloseMethod.MOUSE, this.accessor.partOptions)) {
return;
}
const editor = this.tabsModel.getEditorByIndex(tabIndex);
if (editor) {
if (preventEditorClose(this.tabsModel, editor, EditorCloseMethod.MOUSE, this.accessor.partOptions)) {
return;
}
this.blockRevealActiveTabOnce();
this.closeEditorAction.run({ groupId: this.group.id, editorIndex: index });
this.blockRevealActiveTabOnce();
this.closeEditorAction.run({ groupId: this.groupViewer.id, editorIndex: this.groupViewer.getIndexOfEditor(editor) });
}
}
}));
@ -908,30 +928,30 @@ export class MultiEditorTabsControl extends EditorTabsControl {
// Run action on Enter/Space
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
handled = true;
const editor = this.group.getEditorByIndex(index);
const editor = this.tabsModel.getEditorByIndex(tabIndex);
if (editor) {
this.group.openEditor(editor);
this.groupViewer.openEditor(editor);
}
}
// Navigate in editors
else if ([KeyCode.LeftArrow, KeyCode.RightArrow, KeyCode.UpArrow, KeyCode.DownArrow, KeyCode.Home, KeyCode.End].some(kb => event.equals(kb))) {
let targetIndex: number;
let tabTargetIndex: number;
if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.UpArrow)) {
targetIndex = index - 1;
tabTargetIndex = tabIndex - 1;
} else if (event.equals(KeyCode.RightArrow) || event.equals(KeyCode.DownArrow)) {
targetIndex = index + 1;
tabTargetIndex = tabIndex + 1;
} else if (event.equals(KeyCode.Home)) {
targetIndex = 0;
tabTargetIndex = 0;
} else {
targetIndex = this.group.count - 1;
tabTargetIndex = this.tabsModel.count - 1;
}
const target = this.group.getEditorByIndex(targetIndex);
const target = this.tabsModel.getEditorByIndex(tabTargetIndex);
if (target) {
handled = true;
this.group.openEditor(target, { preserveFocus: true });
(<HTMLElement>tabsContainer.childNodes[targetIndex]).focus();
this.groupViewer.openEditor(target, { preserveFocus: true });
(<HTMLElement>tabsContainer.childNodes[tabTargetIndex]).focus();
}
}
@ -954,13 +974,13 @@ export class MultiEditorTabsControl extends EditorTabsControl {
return; // ignore single taps
}
const editor = this.group.getEditorByIndex(index);
if (editor && this.group.isPinned(editor)) {
const editor = this.tabsModel.getEditorByIndex(tabIndex);
if (editor && this.tabsModel.isPinned(editor)) {
if (this.accessor.partOptions.doubleClickTabToToggleEditorGroupSizes) {
this.accessor.arrangeGroups(GroupsArrangement.TOGGLE, this.group);
this.accessor.arrangeGroups(GroupsArrangement.TOGGLE, this.groupViewer);
}
} else {
this.group.pinEditor(editor);
this.groupViewer.pinEditor(editor);
}
}));
}
@ -969,20 +989,20 @@ export class MultiEditorTabsControl extends EditorTabsControl {
disposables.add(addDisposableListener(tab, EventType.CONTEXT_MENU, e => {
EventHelper.stop(e, true);
const editor = this.group.getEditorByIndex(index);
const editor = this.tabsModel.getEditorByIndex(tabIndex);
if (editor) {
this.onContextMenu(editor, e, tab);
this.onTabContextMenu(editor, e, tab);
}
}, true /* use capture to fix https://github.com/microsoft/vscode/issues/19145 */));
// Drag support
disposables.add(addDisposableListener(tab, EventType.DRAG_START, e => {
const editor = this.group.getEditorByIndex(index);
const editor = this.tabsModel.getEditorByIndex(tabIndex);
if (!editor) {
return;
}
this.editorTransfer.setData([new DraggedEditorIdentifier({ editor, groupId: this.group.id })], DraggedEditorIdentifier.prototype);
this.editorTransfer.setData([new DraggedEditorIdentifier({ editor, groupId: this.groupViewer.id })], DraggedEditorIdentifier.prototype);
if (e.dataTransfer) {
e.dataTransfer.effectAllowed = 'copyMove';
@ -1020,7 +1040,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype);
if (Array.isArray(data)) {
const localDraggedEditor = data[0].identifier;
if (localDraggedEditor.editor === this.group.getEditorByIndex(index) && localDraggedEditor.groupId === this.group.id) {
if (localDraggedEditor.editor === this.tabsModel.getEditorByIndex(tabIndex) && localDraggedEditor.groupId === this.groupViewer.id) {
if (e.dataTransfer) {
e.dataTransfer.dropEffect = 'none';
}
@ -1038,35 +1058,35 @@ export class MultiEditorTabsControl extends EditorTabsControl {
}
}
this.updateDropFeedback(tab, true, index);
this.updateDropFeedback(tab, true, tabIndex);
},
onDragOver: (_, dragDuration) => {
if (dragDuration >= MultiEditorTabsControl.DRAG_OVER_OPEN_TAB_THRESHOLD) {
const draggedOverTab = this.group.getEditorByIndex(index);
if (draggedOverTab && this.group.activeEditor !== draggedOverTab) {
this.group.openEditor(draggedOverTab, { preserveFocus: true });
const draggedOverTab = this.tabsModel.getEditorByIndex(tabIndex);
if (draggedOverTab && this.tabsModel.activeEditor !== draggedOverTab) {
this.groupViewer.openEditor(draggedOverTab, { preserveFocus: true });
}
}
},
onDragLeave: () => {
tab.classList.remove('dragged-over');
this.updateDropFeedback(tab, false, index);
this.updateDropFeedback(tab, false, tabIndex);
},
onDragEnd: () => {
tab.classList.remove('dragged-over');
this.updateDropFeedback(tab, false, index);
this.updateDropFeedback(tab, false, tabIndex);
this.editorTransfer.clearData(DraggedEditorIdentifier.prototype);
},
onDrop: e => {
tab.classList.remove('dragged-over');
this.updateDropFeedback(tab, false, index);
this.updateDropFeedback(tab, false, tabIndex);
this.onDrop(e, index, tabsContainer);
this.onDrop(e, tabIndex, tabsContainer);
}
}));
@ -1078,7 +1098,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
const data = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype);
if (Array.isArray(data)) {
const group = data[0];
if (group.identifier === this.group.id) {
if (group.identifier === this.groupViewer.id) {
return false; // groups cannot be dropped on group it originates from
}
}
@ -1097,10 +1117,10 @@ export class MultiEditorTabsControl extends EditorTabsControl {
return false;
}
private updateDropFeedback(element: HTMLElement, isDND: boolean, index?: number): void {
const isTab = (typeof index === 'number');
const editor = typeof index === 'number' ? this.group.getEditorByIndex(index) : undefined;
const isActiveTab = isTab && !!editor && this.group.isActive(editor);
private updateDropFeedback(element: HTMLElement, isDND: boolean, tabIndex?: number): void {
const isTab = (typeof tabIndex === 'number');
const editor = typeof tabIndex === 'number' ? this.tabsModel.getEditorByIndex(tabIndex) : undefined;
const isActiveTab = isTab && !!editor && this.tabsModel.isActive(editor);
// Background
const noDNDBackgroundColor = isTab ? this.getColor(isActiveTab ? TAB_ACTIVE_BACKGROUND : TAB_INACTIVE_BACKGROUND) : '';
@ -1127,22 +1147,21 @@ export class MultiEditorTabsControl extends EditorTabsControl {
// Build labels and descriptions for each editor
const labels: IEditorInputLabel[] = [];
let activeEditorIndex = -1;
for (let i = 0; i < this.group.editors.length; i++) {
const editor = this.group.editors[i];
let activeEditorTabIndex = -1;
this.tabsModel.getEditors(EditorsOrder.SEQUENTIAL).forEach((editor: EditorInput, tabIndex: number) => {
labels.push({
editor,
name: editor.getName(),
description: editor.getDescription(verbosity),
forceDescription: editor.hasCapability(EditorInputCapabilities.ForceDescription),
title: editor.getTitle(Verbosity.LONG),
ariaLabel: computeEditorAriaLabel(editor, i, this.group, this.editorGroupService.count)
ariaLabel: computeEditorAriaLabel(editor, tabIndex, this.groupViewer, this.editorGroupService.count)
});
if (editor === this.group.activeEditor) {
activeEditorIndex = i;
if (editor === this.tabsModel.activeEditor) {
activeEditorTabIndex = tabIndex;
}
}
});
// Shorten labels as needed
if (shortenDuplicates) {
@ -1151,7 +1170,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
// Remember for fast lookup
this.tabLabels = labels;
this.activeTabLabel = labels[activeEditorIndex];
this.activeTabLabel = labels[activeEditorTabIndex];
}
private shortenTabLabels(labels: IEditorInputLabel[]): void {
@ -1220,9 +1239,9 @@ export class MultiEditorTabsControl extends EditorTabsControl {
// Shorten descriptions
const shortenedDescriptions = shorten(descriptions, this.path.sep);
descriptions.forEach((description, index) => {
descriptions.forEach((description, tabIndex) => {
for (const label of mapDescriptionToDuplicates.get(description) || []) {
label.description = shortenedDescriptions[index];
label.description = shortenedDescriptions[tabIndex];
}
});
}
@ -1260,8 +1279,8 @@ export class MultiEditorTabsControl extends EditorTabsControl {
}
// For each tab
this.forEachTab((editor, index, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => {
this.redrawTab(editor, index, tabContainer, tabLabelWidget, tabLabel, tabActionBar);
this.forEachTab((editor, tabIndex, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => {
this.redrawTab(editor, tabIndex, tabContainer, tabLabelWidget, tabLabel, tabActionBar);
});
// Update Editor Actions Toolbar
@ -1271,12 +1290,12 @@ export class MultiEditorTabsControl extends EditorTabsControl {
this.layout(this.dimensions, options);
}
private redrawTab(editor: EditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar): void {
const isTabSticky = this.group.isSticky(index);
private redrawTab(editor: EditorInput, tabIndex: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar): void {
const isTabSticky = this.tabsModel.isSticky(tabIndex);
const options = this.accessor.partOptions;
// Label
this.redrawTabLabel(editor, index, tabContainer, tabLabelWidget, tabLabel);
this.redrawTabLabel(editor, tabIndex, tabContainer, tabLabelWidget, tabLabel);
// Action
const tabAction = isTabSticky ? this.unpinEditorAction : this.closeEditorAction;
@ -1319,19 +1338,19 @@ export class MultiEditorTabsControl extends EditorTabsControl {
break;
}
tabContainer.style.left = `${index * stickyTabWidth}px`;
tabContainer.style.left = `${tabIndex * stickyTabWidth}px`;
} else {
tabContainer.style.left = 'auto';
}
// Borders / outline
this.redrawTabBorders(index, tabContainer);
this.redrawTabBorders(tabIndex, tabContainer);
// Active / dirty state
this.redrawTabActiveAndDirty(this.accessor.activeGroup === this.group, editor, tabContainer, tabActionBar);
this.redrawTabActiveAndDirty(this.accessor.activeGroup === this.groupViewer, editor, tabContainer, tabActionBar);
}
private redrawTabLabel(editor: EditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel): void {
private redrawTabLabel(editor: EditorInput, tabIndex: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel): void {
const options = this.accessor.partOptions;
// Unless tabs are sticky compact, show the full label and description
@ -1341,7 +1360,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
let forceLabel = false;
let fileDecorationBadges = Boolean(options.decorations?.badges);
let description: string;
if (options.pinnedTabSizing === 'compact' && this.group.isSticky(index)) {
if (options.pinnedTabSizing === 'compact' && this.tabsModel.isSticky(tabIndex)) {
const isShowingIcons = options.showIcons && options.hasIcons;
name = isShowingIcons ? '' : tabLabel.name?.charAt(0).toUpperCase();
description = '';
@ -1368,7 +1387,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
{
title,
extraClasses: coalesce(['tab-label', fileDecorationBadges ? 'tab-label-has-badge' : undefined].concat(editor.getLabelExtraClasses())),
italic: !this.group.isPinned(editor),
italic: !this.tabsModel.isPinned(editor),
forceLabel,
fileDecorations: {
colors: Boolean(options.decorations?.colors),
@ -1387,7 +1406,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
}
private redrawTabActiveAndDirty(isGroupActive: boolean, editor: EditorInput, tabContainer: HTMLElement, tabActionBar: ActionBar): void {
const isTabActive = this.group.isActive(editor);
const isTabActive = this.tabsModel.isActive(editor);
const hasModifiedBorderTop = this.doRedrawTabDirty(isGroupActive, isTabActive, editor, tabContainer);
this.doRedrawTabActive(isGroupActive, !hasModifiedBorderTop, editor, tabContainer, tabActionBar);
@ -1396,7 +1415,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
private doRedrawTabActive(isGroupActive: boolean, allowBorderTop: boolean, editor: EditorInput, tabContainer: HTMLElement, tabActionBar: ActionBar): void {
// Tab is active
if (this.group.isActive(editor)) {
if (this.tabsModel.isActive(editor)) {
// Container
tabContainer.classList.add('active');
@ -1488,9 +1507,9 @@ export class MultiEditorTabsControl extends EditorTabsControl {
return hasModifiedBorderColor;
}
private redrawTabBorders(index: number, tabContainer: HTMLElement): void {
const isTabSticky = this.group.isSticky(index);
const isTabLastSticky = isTabSticky && this.group.stickyCount === index + 1;
private redrawTabBorders(tabIndex: number, tabContainer: HTMLElement): void {
const isTabSticky = this.tabsModel.isSticky(tabIndex);
const isTabLastSticky = isTabSticky && this.tabsModel.stickyCount === tabIndex + 1;
// Borders / Outline
const borderRightColor = ((isTabLastSticky ? this.getColor(TAB_LAST_PINNED_BORDER) : undefined) || this.getColor(TAB_BORDER) || this.getColor(contrastBorder));
@ -1499,7 +1518,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
}
protected override prepareEditorActions(editorActions: IToolbarActions): IToolbarActions {
const isGroupActive = this.accessor.activeGroup === this.group;
const isGroupActive = this.accessor.activeGroup === this.groupViewer;
// Active: allow all actions
if (isGroupActive) {
@ -1531,9 +1550,11 @@ export class MultiEditorTabsControl extends EditorTabsControl {
private computeHeight(): number {
let height: number;
// Wrap: we need to ask `offsetHeight` to get
// the real height of the title area with wrapping.
if (this.accessor.partOptions.wrapTabs && this.tabsAndActionsContainer?.classList.contains('wrapping')) {
if (!this.visible) {
height = 0;
} else if (this.accessor.partOptions.wrapTabs && this.tabsAndActionsContainer?.classList.contains('wrapping')) {
// Wrap: we need to ask `offsetHeight` to get
// the real height of the title area with wrapping.
height = this.tabsAndActionsContainer.offsetHeight;
} else {
height = this.tabHeight;
@ -1547,25 +1568,27 @@ export class MultiEditorTabsControl extends EditorTabsControl {
// Remember dimensions that we get
Object.assign(this.dimensions, dimensions);
// The layout of tabs can be an expensive operation because we access DOM properties
// that can result in the browser doing a full page layout to validate them. To buffer
// this a little bit we try at least to schedule this work on the next animation frame.
if (!this.layoutScheduler.value) {
const scheduledLayout = scheduleAtNextAnimationFrame(() => {
this.doLayout(this.dimensions, this.layoutScheduler.value?.options /* ensure to pick up latest options */);
if (this.visible) {
// The layout of tabs can be an expensive operation because we access DOM properties
// that can result in the browser doing a full page layout to validate them. To buffer
// this a little bit we try at least to schedule this work on the next animation frame.
if (!this.layoutScheduler.value) {
const scheduledLayout = scheduleAtNextAnimationFrame(() => {
this.doLayout(this.dimensions, this.layoutScheduler.value?.options /* ensure to pick up latest options */);
this.layoutScheduler.clear();
});
this.layoutScheduler.clear();
});
this.layoutScheduler.value = { options, dispose: () => scheduledLayout.dispose() };
}
this.layoutScheduler.value = { options, dispose: () => scheduledLayout.dispose() };
}
// Make sure to keep options updated
if (options?.forceRevealActiveTab) {
this.layoutScheduler.value.options = {
...this.layoutScheduler.value.options,
forceRevealActiveTab: true
};
// Make sure to keep options updated
if (options?.forceRevealActiveTab) {
this.layoutScheduler.value.options = {
...this.layoutScheduler.value.options,
forceRevealActiveTab: true
};
}
}
// First time layout: compute the dimensions and store it
@ -1578,13 +1601,9 @@ export class MultiEditorTabsControl extends EditorTabsControl {
private doLayout(dimensions: IEditorTitleControlDimensions, options?: IMultiEditorTabsControlLayoutOptions): void {
// Only layout if we have valid tab index and dimensions
const activeTabAndIndex = this.group.activeEditor ? this.getTabAndIndex(this.group.activeEditor) : undefined;
if (activeTabAndIndex && dimensions.container !== Dimension.None && dimensions.available !== Dimension.None) {
// Tabs
const [activeTab, activeIndex] = activeTabAndIndex;
this.doLayoutTabs(activeTab, activeIndex, dimensions, options);
// Layout tabs
if (dimensions.container !== Dimension.None && dimensions.available !== Dimension.None) {
this.doLayoutTabs(dimensions, options);
}
// Remember the dimensions used in the control so that we can
@ -1598,11 +1617,11 @@ export class MultiEditorTabsControl extends EditorTabsControl {
// to signal this to the outside via a `relayout` call so that
// e.g. the editor control can be adjusted accordingly.
if (oldDimension && oldDimension.height !== newDimension.height) {
this.group.relayout();
this.groupViewer.relayout();
}
}
private doLayoutTabs(activeTab: HTMLElement, activeIndex: number, dimensions: IEditorTitleControlDimensions, options?: IMultiEditorTabsControlLayoutOptions): void {
private doLayoutTabs(dimensions: IEditorTitleControlDimensions, options?: IMultiEditorTabsControlLayoutOptions): void {
// Always first layout tabs with wrapping support even if wrapping
// is disabled. The result indicates if tabs wrap and if not, we
@ -1611,7 +1630,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
// wrapping is disabled (e.g. due to space constraints)
const tabsWrapMultiLine = this.doLayoutTabsWrapping(dimensions);
if (!tabsWrapMultiLine) {
this.doLayoutTabsNonWrapping(activeTab, activeIndex, options);
this.doLayoutTabsNonWrapping(options);
}
}
@ -1744,7 +1763,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
return tabsWrapMultiLine;
}
private doLayoutTabsNonWrapping(activeTab: HTMLElement, activeIndex: number, options?: IMultiEditorTabsControlLayoutOptions): void {
private doLayoutTabsNonWrapping(options?: IMultiEditorTabsControlLayoutOptions): void {
const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar);
//
@ -1771,7 +1790,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
// - shrink: sticky-tabs * TAB_SIZES.shrink
// - normal: 0 (sticky tabs inherit look and feel from non-sticky tabs)
let stickyTabsWidth = 0;
if (this.group.stickyCount > 0) {
if (this.tabsModel.stickyCount > 0) {
let stickyTabWidth = 0;
switch (this.accessor.partOptions.pinnedTabSizing) {
case 'compact':
@ -1782,18 +1801,21 @@ export class MultiEditorTabsControl extends EditorTabsControl {
break;
}
stickyTabsWidth = this.group.stickyCount * stickyTabWidth;
stickyTabsWidth = this.tabsModel.stickyCount * stickyTabWidth;
}
const activeTabAndIndex = this.tabsModel.activeEditor ? this.getTabAndIndex(this.tabsModel.activeEditor) : undefined;
const [activeTab, activeTabIndex] = activeTabAndIndex ?? [undefined, undefined];
// Figure out if active tab is positioned static which has an
// impact on whether to reveal the tab or not later
let activeTabPositionStatic = this.accessor.partOptions.pinnedTabSizing !== 'normal' && this.group.isSticky(activeIndex);
let activeTabPositionStatic = this.accessor.partOptions.pinnedTabSizing !== 'normal' && typeof activeTabIndex === 'number' && this.tabsModel.isSticky(activeTabIndex);
// Special case: we have sticky tabs but the available space for showing tabs
// is little enough that we need to disable sticky tabs sticky positioning
// so that tabs can be scrolled at naturally.
let availableTabsContainerWidth = visibleTabsWidth - stickyTabsWidth;
if (this.group.stickyCount > 0 && availableTabsContainerWidth < MultiEditorTabsControl.TAB_WIDTH.fit) {
if (this.tabsModel.stickyCount > 0 && availableTabsContainerWidth < MultiEditorTabsControl.TAB_WIDTH.fit) {
tabsContainer.classList.add('disable-sticky-tabs');
availableTabsContainerWidth = visibleTabsWidth;
@ -1806,7 +1828,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
let activeTabPosX: number | undefined;
let activeTabWidth: number | undefined;
if (!this.blockRevealActiveTab) {
if (!this.blockRevealActiveTab && activeTab) {
activeTabPosX = activeTab.offsetLeft;
activeTabWidth = activeTab.offsetWidth;
}
@ -1885,28 +1907,42 @@ export class MultiEditorTabsControl extends EditorTabsControl {
}
}
private updateTabsControlVisibility(): void {
const tabsAndActionsContainer = assertIsDefined(this.tabsAndActionsContainer);
tabsAndActionsContainer.classList.toggle('empty', !this.visible);
// Reset dimensions if hidden
if (!this.visible && this.dimensions) {
this.dimensions.used = undefined;
}
}
private get visible(): boolean {
return this.tabsModel.count > 0;
}
private getTabAndIndex(editor: EditorInput): [HTMLElement, number /* index */] | undefined {
const editorIndex = this.group.getIndexOfEditor(editor);
const tab = this.getTabAtIndex(editorIndex);
const tabIndex = this.tabsModel.indexOf(editor);
const tab = this.getTabAtIndex(tabIndex);
if (tab) {
return [tab, editorIndex];
return [tab, tabIndex];
}
return undefined;
}
private getTabAtIndex(editorIndex: number): HTMLElement | undefined {
if (editorIndex >= 0) {
private getTabAtIndex(tabIndex: number): HTMLElement | undefined {
if (tabIndex >= 0) {
const tabsContainer = assertIsDefined(this.tabsContainer);
return tabsContainer.children[editorIndex] as HTMLElement | undefined;
return tabsContainer.children[tabIndex] as HTMLElement | undefined;
}
return undefined;
}
private getLastTab(): HTMLElement | undefined {
return this.getTabAtIndex(this.group.count - 1);
return this.getTabAtIndex(this.tabsModel.count - 1);
}
private blockRevealActiveTabOnce(): void {
@ -1930,27 +1966,33 @@ export class MultiEditorTabsControl extends EditorTabsControl {
return !!findParentWithClass(element, 'action-item', 'tab');
}
private async onDrop(e: DragEvent, targetIndex: number, tabsContainer: HTMLElement): Promise<void> {
private async onDrop(e: DragEvent, targetTabIndex: number, tabsContainer: HTMLElement): Promise<void> {
EventHelper.stop(e, true);
this.updateDropFeedback(tabsContainer, false);
tabsContainer.classList.remove('scroll');
const targetEditorIndex = this.tabsModel instanceof UnstickyEditorGroupModel ? targetTabIndex + this.groupViewer.stickyCount : targetTabIndex;
const options: IEditorOptions = {
sticky: this.tabsModel instanceof StickyEditorGroupModel && this.tabsModel.stickyCount === targetEditorIndex,
index: targetEditorIndex
};
// Check for group transfer
if (this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype)) {
const data = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype);
if (Array.isArray(data)) {
const sourceGroup = this.accessor.getGroup(data[0].identifier);
if (sourceGroup) {
const mergeGroupOptions: IMergeGroupOptions = { index: targetIndex };
const mergeGroupOptions: IMergeGroupOptions = { index: targetEditorIndex };
if (!this.isMoveOperation(e, sourceGroup.id)) {
mergeGroupOptions.mode = MergeGroupMode.COPY_EDITORS;
}
this.accessor.mergeGroup(sourceGroup, this.group, mergeGroupOptions);
this.accessor.mergeGroup(sourceGroup, this.groupViewer, mergeGroupOptions);
}
this.group.focus();
this.groupViewer.focus();
this.groupTransfer.clearData(DraggedEditorGroupIdentifier.prototype);
}
}
@ -1965,16 +2007,16 @@ export class MultiEditorTabsControl extends EditorTabsControl {
// Move editor to target position and index
if (this.isMoveOperation(e, draggedEditor.groupId, draggedEditor.editor)) {
sourceGroup.moveEditor(draggedEditor.editor, this.group, { index: targetIndex });
sourceGroup.moveEditor(draggedEditor.editor, this.groupViewer, options);
}
// Copy editor to target position and index
else {
sourceGroup.copyEditor(draggedEditor.editor, this.group, { index: targetIndex });
sourceGroup.copyEditor(draggedEditor.editor, this.groupViewer, options);
}
}
this.group.focus();
this.groupViewer.focus();
this.editorTransfer.clearData(DraggedEditorIdentifier.prototype);
}
}
@ -1988,11 +2030,11 @@ export class MultiEditorTabsControl extends EditorTabsControl {
const dataTransferItem = await this.treeViewsDragAndDropService.removeDragOperationTransfer(id.identifier);
if (dataTransferItem) {
const treeDropData = await extractTreeDropData(dataTransferItem);
editors.push(...treeDropData.map(editor => ({ ...editor, options: { ...editor.options, pinned: true, index: targetIndex } })));
editors.push(...treeDropData.map(editor => ({ ...editor, options: { ...editor.options, pinned: true, index: targetEditorIndex } })));
}
}
this.editorService.openEditors(editors, this.group, { validateTrust: true });
this.editorService.openEditors(editors, this.groupViewer, { validateTrust: true });
}
this.treeItemsTransfer.clearData(DraggedTreeItemsIdentifier.prototype);
@ -2001,7 +2043,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
// Check for URI transfer
else {
const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: false });
dropHandler.handleDrop(e, () => this.group, () => this.group.focus(), targetIndex);
dropHandler.handleDrop(e, () => this.groupViewer, () => this.groupViewer.focus(), options);
}
}
@ -2012,7 +2054,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
const isCopy = (e.ctrlKey && !isMacintosh) || (e.altKey && isMacintosh);
return !isCopy || sourceGroup === this.group.id;
return (!isCopy || sourceGroup === this.groupViewer.id);
}
override dispose(): void {

View file

@ -0,0 +1,181 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Dimension } from 'vs/base/browser/dom';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
import { IEditorTabsControl } from 'vs/workbench/browser/parts/editor/editorTabsControl';
import { MultiEditorTabsControl } from 'vs/workbench/browser/parts/editor/multiEditorTabsControl';
import { IEditorPartOptions } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { Disposable } from 'vs/base/common/lifecycle';
import { StickyEditorGroupModel, UnstickyEditorGroupModel } from 'vs/workbench/common/editor/filteredEditorGroupModel';
import { IEditorTitleControlDimensions } from 'vs/workbench/browser/parts/editor/editorTitleControl';
import { IReadonlyEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel';
export class MultiRowEditorControl extends Disposable implements IEditorTabsControl {
private readonly stickyEditorTabsControl: IEditorTabsControl;
private readonly unstickyEditorTabsControl: IEditorTabsControl;
constructor(
private parent: HTMLElement,
private accessor: IEditorGroupsAccessor,
private groupViewer: IEditorGroupView,
private model: IReadonlyEditorGroupModel,
@IInstantiationService protected instantiationService: IInstantiationService
) {
super();
const stickyModel = this._register(new StickyEditorGroupModel(this.model));
const unstickyModel = this._register(new UnstickyEditorGroupModel(this.model));
this.stickyEditorTabsControl = this._register(this.instantiationService.createInstance(MultiEditorTabsControl, this.parent, this.accessor, this.groupViewer, stickyModel));
this.unstickyEditorTabsControl = this._register(this.instantiationService.createInstance(MultiEditorTabsControl, this.parent, this.accessor, this.groupViewer, unstickyModel));
this.handlePinnedTabsSeparateRowToolbars();
}
private handlePinnedTabsSeparateRowToolbars(): void {
if (this.groupViewer.count === 0) {
// Do nothing as no tab bar is visible
return;
}
// Ensure action toolbar is only visible once
if (this.groupViewer.count === this.groupViewer.stickyCount) {
this.parent.classList.toggle('two-tab-bars', false);
} else {
this.parent.classList.toggle('two-tab-bars', true);
}
}
private getEditorTabsController(editor: EditorInput): IEditorTabsControl {
return this.model.isSticky(editor) ? this.stickyEditorTabsControl : this.unstickyEditorTabsControl;
}
openEditor(editor: EditorInput): boolean {
const [editorTabController, otherTabController] = this.model.isSticky(editor) ? [this.stickyEditorTabsControl, this.unstickyEditorTabsControl] : [this.unstickyEditorTabsControl, this.stickyEditorTabsControl];
const didChange = editorTabController.openEditor(editor);
if (didChange) {
// HACK: To render all editor tabs on startup, otherwise only one row gets rendered
otherTabController.openEditors([]);
}
return didChange;
}
openEditors(editors: EditorInput[]): boolean {
const stickyEditors = editors.filter(e => this.model.isSticky(e));
const unstickyEditors = editors.filter(e => !this.model.isSticky(e));
const didChangeOpenEditorsSticky = this.stickyEditorTabsControl.openEditors(stickyEditors);
const didChangeOpenEditorsUnSticky = this.unstickyEditorTabsControl.openEditors(unstickyEditors);
return didChangeOpenEditorsSticky || didChangeOpenEditorsUnSticky;
}
beforeCloseEditor(editor: EditorInput): void {
this.getEditorTabsController(editor).beforeCloseEditor(editor);
}
closeEditor(editor: EditorInput): void {
// Has to be called on both tab bars as the editor could be either sticky or not
this.stickyEditorTabsControl.closeEditor(editor);
this.unstickyEditorTabsControl.closeEditor(editor);
this.handleClosedEditors();
}
closeEditors(editors: EditorInput[]): void {
const stickyEditors = editors.filter(e => this.model.isSticky(e));
const unstickyEditors = editors.filter(e => !this.model.isSticky(e));
this.stickyEditorTabsControl.closeEditors(stickyEditors);
this.unstickyEditorTabsControl.closeEditors(unstickyEditors);
this.handleClosedEditors();
}
private handleClosedEditors(): void {
this.handlePinnedTabsSeparateRowToolbars();
}
moveEditor(editor: EditorInput, fromIndex: number, targetIndex: number, stickyStateChange: boolean): void {
if (stickyStateChange) {
// If sticky state changes, move editor between tab bars
if (this.model.isSticky(editor)) {
this.stickyEditorTabsControl.openEditor(editor);
this.unstickyEditorTabsControl.closeEditor(editor);
} else {
this.stickyEditorTabsControl.closeEditor(editor);
this.unstickyEditorTabsControl.openEditor(editor);
}
this.handlePinnedTabsSeparateRowToolbars();
} else {
if (this.model.isSticky(editor)) {
this.stickyEditorTabsControl.moveEditor(editor, fromIndex, targetIndex, stickyStateChange);
} else {
this.unstickyEditorTabsControl.moveEditor(editor, fromIndex - this.model.stickyCount, targetIndex - this.model.stickyCount, stickyStateChange);
}
}
}
pinEditor(editor: EditorInput): void {
this.getEditorTabsController(editor).pinEditor(editor);
}
stickEditor(editor: EditorInput): void {
this.unstickyEditorTabsControl.closeEditor(editor);
this.stickyEditorTabsControl.openEditor(editor);
this.handlePinnedTabsSeparateRowToolbars();
}
unstickEditor(editor: EditorInput): void {
this.stickyEditorTabsControl.closeEditor(editor);
this.unstickyEditorTabsControl.openEditor(editor);
this.handlePinnedTabsSeparateRowToolbars();
}
setActive(isActive: boolean): void {
this.stickyEditorTabsControl.setActive(isActive);
this.unstickyEditorTabsControl.setActive(isActive);
}
updateEditorLabel(editor: EditorInput): void {
this.getEditorTabsController(editor).updateEditorLabel(editor);
}
updateEditorDirty(editor: EditorInput): void {
this.getEditorTabsController(editor).updateEditorDirty(editor);
}
updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void {
this.stickyEditorTabsControl.updateOptions(oldOptions, newOptions);
this.unstickyEditorTabsControl.updateOptions(oldOptions, newOptions);
}
layout(dimensions: IEditorTitleControlDimensions): Dimension {
const stickyDimensions = this.stickyEditorTabsControl.layout(dimensions);
const unstickyDimensions = this.unstickyEditorTabsControl.layout(dimensions);
return new Dimension(
dimensions.container.width,
stickyDimensions.height + unstickyDimensions.height
);
}
getHeight(): number {
return this.stickyEditorTabsControl.getHeight() + this.unstickyEditorTabsControl.getHeight();
}
public override dispose(): void {
this.parent.classList.toggle('two-tab-bars', false);
super.dispose();
}
}

View file

@ -55,7 +55,7 @@ export class SingleEditorTabsControl extends EditorTabsControl {
this._register(addDisposableListener(this.editorLabel.element, EventType.CLICK, e => this.onTitleLabelClick(e)));
// Breadcrumbs
this.breadcrumbsControlFactory = this._register(this.instantiationService.createInstance(BreadcrumbsControlFactory, labelContainer, this.group, {
this.breadcrumbsControlFactory = this._register(this.instantiationService.createInstance(BreadcrumbsControlFactory, labelContainer, this.groupViewer, {
showFileIcons: false,
showSymbolIcons: true,
showDecorationColors: false,
@ -92,8 +92,8 @@ export class SingleEditorTabsControl extends EditorTabsControl {
// Context Menu
for (const event of [EventType.CONTEXT_MENU, TouchEventType.Contextmenu]) {
this._register(addDisposableListener(titleContainer, event, e => {
if (this.group.activeEditor) {
this.onContextMenu(this.group.activeEditor, e, titleContainer);
if (this.tabsModel.activeEditor) {
this.onTabContextMenu(this.tabsModel.activeEditor, e, titleContainer);
}
}));
}
@ -109,15 +109,15 @@ export class SingleEditorTabsControl extends EditorTabsControl {
private onTitleDoubleClick(e: MouseEvent): void {
EventHelper.stop(e);
this.group.pinEditor();
this.groupViewer.pinEditor();
}
private onTitleAuxClick(e: MouseEvent): void {
if (e.button === 1 /* Middle Button */ && this.group.activeEditor) {
if (e.button === 1 /* Middle Button */ && this.tabsModel.activeEditor) {
EventHelper.stop(e, true /* for https://github.com/microsoft/vscode/issues/56715 */);
if (!preventEditorClose(this.group, this.group.activeEditor, EditorCloseMethod.MOUSE, this.accessor.partOptions)) {
this.group.closeEditor(this.group.activeEditor);
if (!preventEditorClose(this.tabsModel, this.tabsModel.activeEditor, EditorCloseMethod.MOUSE, this.accessor.partOptions)) {
this.groupViewer.closeEditor(this.tabsModel.activeEditor);
}
}
}
@ -231,9 +231,9 @@ export class SingleEditorTabsControl extends EditorTabsControl {
private ifActiveEditorChanged(fn: () => void): boolean {
if (
!this.activeLabel.editor && this.group.activeEditor || // active editor changed from null => editor
this.activeLabel.editor && !this.group.activeEditor || // active editor changed from editor => null
(!this.activeLabel.editor || !this.group.isActive(this.activeLabel.editor)) // active editor changed from editorA => editorB
!this.activeLabel.editor && this.tabsModel.activeEditor || // active editor changed from null => editor
this.activeLabel.editor && !this.tabsModel.activeEditor || // active editor changed from editor => null
(!this.activeLabel.editor || !this.tabsModel.isActive(this.activeLabel.editor)) // active editor changed from editorA => editorB
) {
fn();
@ -244,27 +244,27 @@ export class SingleEditorTabsControl extends EditorTabsControl {
}
private ifActiveEditorPropertiesChanged(fn: () => void): void {
if (!this.activeLabel.editor || !this.group.activeEditor) {
if (!this.activeLabel.editor || !this.tabsModel.activeEditor) {
return; // need an active editor to check for properties changed
}
if (this.activeLabel.pinned !== this.group.isPinned(this.group.activeEditor)) {
if (this.activeLabel.pinned !== this.tabsModel.isPinned(this.tabsModel.activeEditor)) {
fn(); // only run if pinned state has changed
}
}
private ifEditorIsActive(editor: EditorInput, fn: () => void): void {
if (this.group.isActive(editor)) {
if (this.tabsModel.isActive(editor)) {
fn(); // only run if editor is current active
}
}
private redraw(): void {
const editor = this.group.activeEditor ?? undefined;
const editor = this.tabsModel.activeEditor ?? undefined;
const options = this.accessor.partOptions;
const isEditorPinned = editor ? this.group.isPinned(editor) : false;
const isGroupActive = this.accessor.activeGroup === this.group;
const isEditorPinned = editor ? this.tabsModel.isPinned(editor) : false;
const isGroupActive = this.accessor.activeGroup === this.groupViewer;
this.activeLabel = { editor, pinned: isEditorPinned };
@ -345,7 +345,7 @@ export class SingleEditorTabsControl extends EditorTabsControl {
}
protected override prepareEditorActions(editorActions: IToolbarActions): IToolbarActions {
const isGroupActive = this.accessor.activeGroup === this.group;
const isGroupActive = this.accessor.activeGroup === this.groupViewer;
// Active: allow all actions
if (isGroupActive) {

View file

@ -7,6 +7,7 @@ import { reset } from 'vs/base/browser/dom';
import { BaseActionViewItem, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover';
import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { IAction, SubmenuAction } from 'vs/base/common/actions';
import { Codicon } from 'vs/base/common/codicons';
import { Emitter, Event } from 'vs/base/common/event';
@ -182,6 +183,16 @@ class CommandCenterCenterViewItem extends BaseActionViewItem {
});
toolbar.setActions(group);
this._store.add(toolbar);
// spacer
if (i < groups.length - 1) {
const icon = renderIcon(Codicon.circleSmallFilled);
icon.style.padding = '0 12px';
icon.style.height = '100%';
icon.style.opacity = '0.5';
container.appendChild(icon);
}
}
}

View file

@ -179,7 +179,7 @@
}
.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center.multiple {
justify-content: space-between;
justify-content: flex-start;
padding: 0 12px;
}

View file

@ -182,6 +182,11 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
],
'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'pinnedTabSizing' }, "Controls the size of pinned editor tabs. Pinned tabs are sorted to the beginning of all opened tabs and typically do not close until unpinned. This value is ignored when `#workbench.editor.showTabs#` is disabled.")
},
'workbench.editor.pinnedTabsOnSeparateRow': {
'type': 'boolean',
'default': false,
'markdownDescription': localize('workbench.editor.pinnedTabsOnSeparateRow', "When enabled, displays pinned tabs in a separate row above all other tabs. This value is ignored when `#workbench.editor.showTabs#` is disabled."),
},
'workbench.editor.preventPinnedEditorClose': {
'type': 'string',
'enum': ['keyboardAndMouse', 'keyboard', 'mouse', 'never'],

View file

@ -27,6 +27,7 @@ import { IErrorWithActions, createErrorWithActions, isErrorWithActions } from 'v
import { IAction, toAction } from 'vs/base/common/actions';
import Severity from 'vs/base/common/severity';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { IReadonlyEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel';
// Static values for editor contributions
export const EditorExtensions = {
@ -1098,6 +1099,7 @@ interface IEditorPartConfiguration {
tabSizingFixedMinWidth?: number;
tabSizingFixedMaxWidth?: number;
pinnedTabSizing?: 'normal' | 'compact' | 'shrink';
pinnedTabsOnSeparateRow?: boolean;
tabHeight?: 'normal' | 'compact';
preventPinnedEditorClose?: PreventPinnedEditorClose;
titleScrollbarSizing?: 'default' | 'large';
@ -1350,7 +1352,7 @@ export enum EditorCloseMethod {
MOUSE
}
export function preventEditorClose(group: IEditorGroup, editor: EditorInput, method: EditorCloseMethod, configuration: IEditorPartConfiguration): boolean {
export function preventEditorClose(group: IEditorGroup | IReadonlyEditorGroupModel, editor: EditorInput, method: EditorCloseMethod, configuration: IEditorPartConfiguration): boolean {
if (!group.isSticky(editor)) {
return false; // only interested in sticky editors
}

View file

@ -163,7 +163,37 @@ interface IEditorCloseResult {
readonly sticky: boolean;
}
export class EditorGroupModel extends Disposable {
export interface IReadonlyEditorGroupModel {
readonly onDidModelChange: Event<IGroupModelChangeEvent>;
readonly id: GroupIdentifier;
readonly count: number;
readonly stickyCount: number;
readonly isLocked: boolean;
readonly activeEditor: EditorInput | null;
readonly previewEditor: EditorInput | null;
getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): readonly EditorInput[];
getEditorByIndex(index: number): EditorInput | undefined;
indexOf(editor: EditorInput | IUntypedEditorInput | null, editors?: EditorInput[], options?: IMatchEditorOptions): number;
isActive(editor: EditorInput | IUntypedEditorInput): boolean;
isPinned(editorOrIndex: EditorInput | number): boolean;
isSticky(editorOrIndex: EditorInput | number): boolean;
isFirst(editor: EditorInput): boolean;
isLast(editor: EditorInput): boolean;
findEditor(editor: EditorInput | null, options?: IMatchEditorOptions): [EditorInput, number /* index */] | undefined;
contains(editor: EditorInput | IUntypedEditorInput, options?: IMatchEditorOptions): boolean;
}
interface IEditorGroupModel extends IReadonlyEditorGroupModel {
openEditor(editor: EditorInput, options?: IEditorOpenOptions): IEditorOpenResult;
closeEditor(editor: EditorInput, context?: EditorCloseContext, openNext?: boolean): IEditorCloseResult | undefined;
moveEditor(editor: EditorInput, toIndex: number): EditorInput | undefined;
setActive(editor: EditorInput | undefined): EditorInput | undefined;
}
export class EditorGroupModel extends Disposable implements IEditorGroupModel {
private static IDS = 0;

View file

@ -69,8 +69,6 @@ export abstract class EditorInput extends AbstractEditorInput {
*/
readonly onWillDispose = this._onWillDispose.event;
private disposed: boolean = false;
/**
* Optional: subclasses can override to implement
* custom confirmation on close behavior.
@ -316,12 +314,11 @@ export abstract class EditorInput extends AbstractEditorInput {
* Returns if this editor is disposed.
*/
isDisposed(): boolean {
return this.disposed;
return this._store.isDisposed;
}
override dispose(): void {
if (!this.disposed) {
this.disposed = true;
if (!this.isDisposed()) {
this._onWillDispose.fire();
}

View file

@ -17,7 +17,6 @@ export class EditorModel extends Disposable implements IEditorModel {
private readonly _onWillDispose = this._register(new Emitter<void>());
readonly onWillDispose = this._onWillDispose.event;
private disposed = false;
private resolved = false;
/**
@ -38,14 +37,13 @@ export class EditorModel extends Disposable implements IEditorModel {
* Find out if this model has been disposed.
*/
isDisposed(): boolean {
return this.disposed;
return this._store.isDisposed;
}
/**
* Subclasses should implement to free resources that have been claimed through loading.
*/
override dispose(): void {
this.disposed = true;
this._onWillDispose.fire();
super.dispose();

View file

@ -0,0 +1,158 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IUntypedEditorInput, IMatchEditorOptions, EditorsOrder, GroupIdentifier } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { Emitter } from 'vs/base/common/event';
import { IGroupModelChangeEvent, IReadonlyEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel';
import { Disposable } from 'vs/base/common/lifecycle';
abstract class FilteredEditorGroupModel extends Disposable implements IReadonlyEditorGroupModel {
private readonly _onDidModelChange = this._register(new Emitter<IGroupModelChangeEvent>());
readonly onDidModelChange = this._onDidModelChange.event;
constructor(
protected readonly model: IReadonlyEditorGroupModel
) {
super();
this._register(this.model.onDidModelChange(e => {
const candidateOrIndex = e.editorIndex ?? e.editor;
if (candidateOrIndex !== undefined) {
if (!this.filter(candidateOrIndex)) {
return; // exclude events for excluded items
}
}
this._onDidModelChange.fire(e);
}));
}
get id(): GroupIdentifier { return this.model.id; }
get isLocked(): boolean { return this.model.isLocked; }
get stickyCount(): number { return this.model.stickyCount; }
get activeEditor(): EditorInput | null { return this.model.activeEditor && this.filter(this.model.activeEditor) ? this.model.activeEditor : null; }
get previewEditor(): EditorInput | null { return this.model.previewEditor && this.filter(this.model.previewEditor) ? this.model.previewEditor : null; }
isPinned(editorOrIndex: EditorInput | number): boolean { return this.model.isPinned(editorOrIndex); }
isSticky(editorOrIndex: EditorInput | number): boolean { return this.model.isSticky(editorOrIndex); }
isActive(editor: EditorInput | IUntypedEditorInput): boolean { return this.model.isActive(editor); }
getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): readonly EditorInput[] {
const editors = this.model.getEditors(order, options);
return editors.filter(e => this.filter(e));
}
findEditor(candidate: EditorInput | null, options?: IMatchEditorOptions): [EditorInput, number] | undefined {
const result = this.model.findEditor(candidate, options);
if (!result) {
return undefined;
}
return this.filter(result[1]) ? result : undefined;
}
abstract get count(): number;
abstract isFirst(editor: EditorInput): boolean;
abstract isLast(editor: EditorInput): boolean;
abstract getEditorByIndex(index: number): EditorInput | undefined;
abstract indexOf(editor: EditorInput | IUntypedEditorInput | null, editors?: EditorInput[], options?: IMatchEditorOptions): number;
abstract contains(editor: EditorInput | IUntypedEditorInput, options?: IMatchEditorOptions): boolean;
protected abstract filter(editorOrIndex: EditorInput | number): boolean;
}
export class StickyEditorGroupModel extends FilteredEditorGroupModel {
get count(): number { return this.model.stickyCount; }
override getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): readonly EditorInput[] {
if (options?.excludeSticky) {
return [];
}
if (order === EditorsOrder.SEQUENTIAL) {
return this.model.getEditors(EditorsOrder.SEQUENTIAL).slice(0, this.model.stickyCount);
}
return super.getEditors(order, options);
}
override isSticky(editorOrIndex: number | EditorInput): boolean {
return true;
}
isFirst(editor: EditorInput): boolean {
return this.model.isFirst(editor);
}
isLast(editor: EditorInput): boolean {
return this.model.indexOf(editor) === this.model.stickyCount - 1;
}
getEditorByIndex(index: number): EditorInput | undefined {
return index < this.count ? this.model.getEditorByIndex(index) : undefined;
}
indexOf(editor: EditorInput | IUntypedEditorInput | null, editors?: EditorInput[], options?: IMatchEditorOptions): number {
const editorIndex = this.model.indexOf(editor, editors, options);
if (editorIndex < 0 || editorIndex >= this.model.stickyCount) {
return -1;
}
return editorIndex;
}
contains(candidate: EditorInput | IUntypedEditorInput, options?: IMatchEditorOptions): boolean {
const editorIndex = this.model.indexOf(candidate, undefined, options);
return editorIndex >= 0 && editorIndex < this.model.stickyCount;
}
protected filter(candidateOrIndex: EditorInput | number): boolean {
return this.model.isSticky(candidateOrIndex);
}
}
export class UnstickyEditorGroupModel extends FilteredEditorGroupModel {
get count(): number { return this.model.count - this.model.stickyCount; }
override get stickyCount(): number { return 0; }
override isSticky(editorOrIndex: number | EditorInput): boolean {
return false;
}
override getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): readonly EditorInput[] {
if (order === EditorsOrder.SEQUENTIAL) {
return this.model.getEditors(EditorsOrder.SEQUENTIAL).slice(this.model.stickyCount);
}
return super.getEditors(order, options);
}
isFirst(editor: EditorInput): boolean {
return this.model.indexOf(editor) === this.model.stickyCount;
}
isLast(editor: EditorInput): boolean {
return this.model.isLast(editor);
}
getEditorByIndex(index: number): EditorInput | undefined {
return index >= 0 ? this.model.getEditorByIndex(index + this.model.stickyCount) : undefined;
}
indexOf(editor: EditorInput | IUntypedEditorInput | null, editors?: EditorInput[], options?: IMatchEditorOptions): number {
const editorIndex = this.model.indexOf(editor, editors, options);
if (editorIndex < this.model.stickyCount || editorIndex >= this.model.count) {
return -1;
}
return editorIndex - this.model.stickyCount;
}
contains(candidate: EditorInput | IUntypedEditorInput, options?: IMatchEditorOptions): boolean {
const editorIndex = this.model.indexOf(candidate, undefined, options);
return editorIndex >= this.model.stickyCount && editorIndex < this.model.count;
}
protected filter(candidateOrIndex: EditorInput | number): boolean {
return !this.model.isSticky(candidateOrIndex);
}
}

View file

@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from 'vs/base/common/cancellation';
import { Iterable } from 'vs/base/common/iterator';
import { Disposable } from 'vs/base/common/lifecycle';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { Position } from 'vs/editor/common/core/position';
@ -16,6 +15,7 @@ import { ITextModel } from 'vs/editor/common/model';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { localize } from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Registry } from 'vs/platform/registry/common/platform';
import { inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
@ -26,6 +26,8 @@ import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart';
import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget';
import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { chatSlashCommandBackground, chatSlashCommandForeground } from 'vs/workbench/contrib/chat/common/chatColors';
import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart } from '../../common/chatRequestParser';
import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser';
import { IChatService, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
@ -42,11 +44,10 @@ class InputEditorDecorations extends Disposable {
constructor(
private readonly widget: IChatWidget,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ICodeEditorService private readonly codeEditorService: ICodeEditorService,
@IThemeService private readonly themeService: IThemeService,
@IChatService private readonly chatService: IChatService,
@IChatVariablesService private readonly chatVariablesService: IChatVariablesService,
@IChatAgentService private readonly chatAgentService: IChatAgentService
) {
super();
@ -94,7 +95,11 @@ class InputEditorDecorations extends Disposable {
private async updateInputEditorDecorations() {
const inputValue = this.widget.inputEditor.getValue();
const slashCommands = await this.widget.getSlashCommands(); // TODO this async call can lead to a flicker of the placeholder text when switching editor tabs
const agents = this.chatAgentService.getAgents();
const viewModel = this.widget.viewModel;
if (!viewModel) {
return;
}
if (!inputValue) {
const extensionPlaceholder = this.widget.viewModel?.inputPlaceholder;
@ -122,39 +127,27 @@ class InputEditorDecorations extends Disposable {
return;
}
// TODO@roblourens need some kind of parser for queries
const parsedRequest = await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(viewModel.sessionId, inputValue);
let placeholderDecoration: IDecorationOptions[] | undefined;
const usedAgent = inputValue && agents.find(a => inputValue.startsWith(`@${a.id} `));
const agentPart = parsedRequest.find((p): p is ChatRequestAgentPart => p instanceof ChatRequestAgentPart);
const agentSubcommandPart = parsedRequest.find((p): p is ChatRequestAgentSubcommandPart => p instanceof ChatRequestAgentSubcommandPart);
const slashCommandPart = parsedRequest.find((p): p is ChatRequestSlashCommandPart => p instanceof ChatRequestSlashCommandPart);
let usedSubcommand: string | undefined;
let subCommandPosition: number | undefined;
if (usedAgent) {
const subCommandReg = /\/(\w+)(\s|$)/g;
let subCommandMatch: RegExpExecArray | null;
while (subCommandMatch = subCommandReg.exec(inputValue)) {
const maybeCommand = subCommandMatch[1];
usedSubcommand = usedAgent.metadata.subCommands.find(agentCommand => maybeCommand === agentCommand.name)?.name;
if (usedSubcommand) {
subCommandPosition = subCommandMatch.index;
break;
}
}
}
if (usedAgent && inputValue === `@${usedAgent.id} `) {
const onlyAgentAndWhitespace = agentPart && parsedRequest.every(p => p instanceof ChatRequestTextPart && !p.text.trim().length || p instanceof ChatRequestAgentPart);
if (onlyAgentAndWhitespace) {
// Agent reference with no other text - show the placeholder
if (usedAgent.metadata.description) {
if (agentPart.agent.metadata.description) {
placeholderDecoration = [{
range: {
startLineNumber: 1,
endLineNumber: 1,
startColumn: usedAgent.id.length,
startColumn: inputValue.length,
endColumn: 1000
},
renderOptions: {
after: {
contentText: usedAgent.metadata.description,
contentText: agentPart.agent.metadata.description,
color: this.getPlaceholderColor(),
}
}
@ -162,22 +155,22 @@ class InputEditorDecorations extends Disposable {
}
}
const command = !usedAgent && inputValue && slashCommands?.find(c => inputValue.startsWith(`/${c.command} `));
if (command && inputValue === `/${command.command} `) {
const onlySlashCommandAndWhitespace = slashCommandPart && parsedRequest.every(p => p instanceof ChatRequestTextPart && !p.text.trim().length || p instanceof ChatRequestSlashCommandPart);
if (onlySlashCommandAndWhitespace) {
// Command reference with no other text - show the placeholder
const isFollowupSlashCommand = this._previouslyUsedSlashCommands.has(command.command);
const shouldRenderFollowupPlaceholder = command.followupPlaceholder && isFollowupSlashCommand;
if (shouldRenderFollowupPlaceholder || command.detail) {
const isFollowupSlashCommand = this._previouslyUsedSlashCommands.has(slashCommandPart.slashCommand.command);
const shouldRenderFollowupPlaceholder = isFollowupSlashCommand && slashCommandPart.slashCommand.followupPlaceholder;
if (shouldRenderFollowupPlaceholder || slashCommandPart.slashCommand.detail) {
placeholderDecoration = [{
range: {
startLineNumber: 1,
endLineNumber: 1,
startColumn: command ? command.command.length : 1,
startColumn: inputValue.length,
endColumn: 1000
},
renderOptions: {
after: {
contentText: shouldRenderFollowupPlaceholder ? command.followupPlaceholder : command.detail,
contentText: shouldRenderFollowupPlaceholder ? slashCommandPart.slashCommand.followupPlaceholder : slashCommandPart.slashCommand.detail,
color: this.getPlaceholderColor(),
}
}
@ -187,64 +180,24 @@ class InputEditorDecorations extends Disposable {
this.widget.inputEditor.setDecorationsByType(decorationDescription, placeholderDecorationType, placeholderDecoration ?? []);
// TODO@roblourens The way these numbers are computed aren't totally correct...
const textDecorations: IDecorationOptions[] | undefined = [];
if (usedAgent) {
textDecorations.push(
{
range: {
startLineNumber: 1,
endLineNumber: 1,
startColumn: 1,
endColumn: usedAgent.id.length + 2
}
}
);
if (usedSubcommand) {
textDecorations.push(
{
range: {
startLineNumber: 1,
endLineNumber: 1,
startColumn: subCommandPosition! + 1,
endColumn: subCommandPosition! + usedSubcommand.length + 2
}
}
);
if (agentPart) {
textDecorations.push({ range: agentPart.editorRange });
if (agentSubcommandPart) {
textDecorations.push({ range: agentSubcommandPart.editorRange });
}
}
if (command) {
textDecorations.push(
{
range: {
startLineNumber: 1,
endLineNumber: 1,
startColumn: 1,
endColumn: command.command.length + 2
}
}
);
if (slashCommandPart) {
textDecorations.push({ range: slashCommandPart.editorRange });
}
this.widget.inputEditor.setDecorationsByType(decorationDescription, slashCommandTextDecorationType, textDecorations);
const variables = this.chatVariablesService.getVariables();
const variableReg = /(^|\s)@(\w+)(:\d+)?(?=(\s|$))/ig;
let match: RegExpMatchArray | null;
const varDecorations: IDecorationOptions[] = [];
while (match = variableReg.exec(inputValue)) {
const varName = match[2];
if (Iterable.find(variables, v => v.name === varName)) {
varDecorations.push({
range: {
startLineNumber: 1,
endLineNumber: 1,
startColumn: match.index! + match[1].length + 1,
endColumn: match.index! + match[0].length + 1
}
});
}
const variableParts = parsedRequest.filter((p): p is ChatRequestVariablePart => p instanceof ChatRequestVariablePart);
for (const variable of variableParts) {
varDecorations.push({ range: variable.editorRange });
}
this.widget.inputEditor.setDecorationsByType(decorationDescription, variableTextDecorationType, varDecorations);
@ -286,7 +239,7 @@ class SlashCommandCompletions extends Disposable {
constructor(
@ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService,
@IChatWidgetService private readonly chatWidgetService: IChatWidgetService,
@IChatAgentService private readonly chatAgentService: IChatAgentService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
) {
super();
@ -295,23 +248,17 @@ class SlashCommandCompletions extends Disposable {
triggerCharacters: ['/'],
provideCompletionItems: async (model: ITextModel, _position: Position, _context: CompletionContext, _token: CancellationToken) => {
const widget = this.chatWidgetService.getWidgetByInputUri(model.uri);
if (!widget) {
if (!widget || !widget.viewModel) {
return null;
}
const firstLine = model.getLineContent(1).trim();
const agents = this.chatAgentService.getAgents();
const usedAgent = firstLine.startsWith('@') && agents.find(a => firstLine.startsWith(`@${a.id}`));
const parsedRequest = await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue());
const usedAgent = parsedRequest.find(p => p instanceof ChatRequestAgentPart);
if (usedAgent) {
// No (classic) global slash commands when an agent is used
return;
}
if (model.getValueInRange(new Range(1, 1, 1, 2)) !== '/' && model.getValueLength() > 0) {
return null;
}
const slashCommands = await widget.getSlashCommands();
if (!slashCommands) {
return null;
@ -342,20 +289,29 @@ class AgentCompletions extends Disposable {
constructor(
@ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService,
@IChatWidgetService private readonly chatWidgetService: IChatWidgetService,
@IChatAgentService private readonly chatAgentService: IChatAgentService
@IChatAgentService private readonly chatAgentService: IChatAgentService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
) {
super();
this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, {
_debugDisplayName: 'chatAgent',
triggerCharacters: ['@'],
provideCompletionItems: async (model: ITextModel, _position: Position, _context: CompletionContext, _token: CancellationToken) => {
provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => {
const widget = this.chatWidgetService.getWidgetByInputUri(model.uri);
if (!widget) {
if (!widget || !widget.viewModel) {
return null;
}
if (model.getValueInRange(new Range(1, 1, 1, 2)) !== '@' && model.getValueLength() > 0) {
const parsedRequest = await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue());
const usedAgent = parsedRequest.find(p => p instanceof ChatRequestAgentPart);
if (usedAgent && !Range.containsPosition(usedAgent.editorRange, position)) {
// Only one agent allowed
return;
}
const range = computeCompletionRanges(model, position, /@\w*/g);
if (!range) {
return null;
}
@ -367,10 +323,8 @@ class AgentCompletions extends Disposable {
label: withAt,
insertText: `${withAt} `,
detail: c.metadata.description,
range: new Range(1, 1, 1, 1),
// sortText: 'a'.repeat(i + 1),
range,
kind: CompletionItemKind.Text, // The icons are disabled here anyway
// command: c.executeImmediately ? { id: SubmitAction.ID, title: withAt, arguments: [{ widget, inputValue: `${withAt} ` }] } : undefined,
};
})
};
@ -382,40 +336,31 @@ class AgentCompletions extends Disposable {
triggerCharacters: ['/'],
provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => {
const widget = this.chatWidgetService.getWidgetByInputUri(model.uri);
if (!widget) {
if (!widget || !widget.viewModel) {
return;
}
const firstLine = model.getLineContent(1).trim();
if (!firstLine.startsWith('@')) {
return;
}
const agents = this.chatAgentService.getAgents();
const usedAgent = agents.find(a => firstLine.startsWith(`@${a.id}`));
const parsedRequest = await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue());
const usedAgent = parsedRequest.find((p): p is ChatRequestAgentPart => p instanceof ChatRequestAgentPart);
if (!usedAgent) {
return;
}
const maybeCommands = model.getValue().split(/\s+/).filter(w => w.startsWith('/'));
const usedSubcommand = usedAgent.metadata.subCommands.find(agentCommand => maybeCommands.some(c => c === `/${agentCommand.name}`));
const usedSubcommand = parsedRequest.find(p => p instanceof ChatRequestAgentSubcommandPart);
if (usedSubcommand) {
// Only one allowed
return;
}
return <CompletionList>{
suggestions: usedAgent.metadata.subCommands.map((c, i) => {
suggestions: usedAgent.agent.metadata.subCommands.map((c, i) => {
const withSlash = `/${c.name}`;
return <CompletionItem>{
label: withSlash,
insertText: `${withSlash} `,
detail: c.description,
range: new Range(1, position.column - 1, 1, position.column - 1),
// sortText: 'a'.repeat(i + 1),
kind: CompletionItemKind.Text, // The icons are disabled here anyway
// command: c.executeImmediately ? { id: SubmitAction.ID, title: withAt, arguments: [{ widget, inputValue: `${withAt} ` }] } : undefined,
};
})
};
@ -530,6 +475,25 @@ function sortSlashCommandsByYieldTo<T extends {
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AgentCompletions, LifecyclePhase.Eventually);
function computeCompletionRanges(model: ITextModel, position: Position, reg: RegExp): { insert: Range; replace: Range } | undefined {
const varWord = getWordAtText(position.column, reg, model.getLineContent(position.lineNumber), 0);
if (!varWord && model.getWordUntilPosition(position).word) {
// inside a "normal" word
return;
}
let insert: Range;
let replace: Range;
if (!varWord) {
insert = replace = Range.fromPositions(position);
} else {
insert = new Range(position.lineNumber, varWord.startColumn, position.lineNumber, position.column);
replace = new Range(position.lineNumber, varWord.startColumn, position.lineNumber, varWord.endColumn);
}
return { insert, replace };
}
class VariableCompletions extends Disposable {
private static readonly VariableNameDef = /@\w*/g; // MUST be using `g`-flag
@ -552,21 +516,11 @@ class VariableCompletions extends Disposable {
return null;
}
const varWord = getWordAtText(position.column, VariableCompletions.VariableNameDef, model.getLineContent(position.lineNumber), 0);
if (!varWord && model.getWordUntilPosition(position).word) {
// inside a "normal" word
const range = computeCompletionRanges(model, position, VariableCompletions.VariableNameDef);
if (!range) {
return null;
}
let insert: Range;
let replace: Range;
if (!varWord) {
insert = replace = Range.fromPositions(position);
} else {
insert = new Range(position.lineNumber, varWord.startColumn, position.lineNumber, position.column);
replace = new Range(position.lineNumber, varWord.startColumn, position.lineNumber, varWord.endColumn);
}
const history = widget.viewModel!.getItems()
.filter(isResponseVM);
@ -577,14 +531,14 @@ class VariableCompletions extends Disposable {
detail: h.response.asString(),
insertText: `@response:${String(i + 1).padStart(String(history.length).length, '0')} `,
kind: CompletionItemKind.Text,
range: { insert, replace },
range,
})) : [];
const variableItems = Array.from(this.chatVariablesService.getVariables()).map(v => {
const withAt = `@${v.name}`;
return <CompletionItem>{
label: withAt,
range: { insert, replace },
range,
insertText: withAt + ' ',
detail: v.description,
kind: CompletionItemKind.Text, // The icons are disabled here anyway,

View file

@ -98,6 +98,7 @@ export interface IChatAgentService {
registerAgent(data: IChatAgentData, callback: IChatAgentCallback): IDisposable;
invokeAgent(id: string, prompt: string, progress: IProgress<IChatAgentFragment>, history: IChatMessage[], token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void>;
getAgents(): Array<IChatAgentData>;
getAgent(id: string): IChatAgentData | undefined;
hasAgent(id: string): boolean;
}
@ -161,6 +162,11 @@ export class ChatAgentService extends Disposable implements IChatAgentService {
return this._agents.has(id);
}
getAgent(id: string): IChatAgentData | undefined {
const data = this._agents.get(id);
return data?.data;
}
async invokeAgent(id: string, prompt: string, progress: IProgress<IChatAgentFragment>, history: IChatMessage[], token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void> {
const data = this._agents.get(id);
if (!data) {

View file

@ -0,0 +1,170 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from 'vs/base/common/cancellation';
import { OffsetRange } from 'vs/editor/common/core/offsetRange';
import { IPosition, Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatService, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
const variableOrAgentReg = /^@([\w_\-]+)(:\d+)?(?=(\s|$))/i; // An @-variable with an optional numeric : arg (@response:2)
const slashReg = /\/([\w_-]+)(?=(\s|$))/i; // A / command
export class ChatRequestParser {
constructor(
@IChatAgentService private readonly agentService: IChatAgentService,
@IChatVariablesService private readonly variableService: IChatVariablesService,
@IChatService private readonly chatService: IChatService,
) { }
async parseChatRequest(sessionId: string, message: string): Promise<IParsedChatRequestPart[]> {
const parts: IParsedChatRequestPart[] = [];
let lineNumber = 1;
let column = 1;
for (let i = 0; i < message.length; i++) {
const previousChar = message.charAt(i - 1);
const char = message.charAt(i);
let newPart: IParsedChatRequestPart | undefined;
if (char === '@' && (previousChar === ' ' || i === 0)) {
newPart = this.tryToParseVariableOrAgent(message.slice(i), i, new Position(lineNumber, column), parts);
} else if (char === '/' && (previousChar === ' ' || i === 0)) {
// TODO try to make this sync
newPart = await this.tryToParseSlashCommand(sessionId, message.slice(i), i, new Position(lineNumber, column), parts);
}
if (newPart) {
if (i !== 0) {
// Insert a part for all the text we passed over, then insert the new parsed part
const previousPart = parts.at(-1);
const previousPartEnd = previousPart?.range.endExclusive ?? 0;
const previousPartEditorRangeEndLine = previousPart?.editorRange.endLineNumber ?? 1;
const previousPartEditorRangeEndCol = previousPart?.editorRange.endColumn ?? 1;
parts.push(new ChatRequestTextPart(
new OffsetRange(previousPartEnd, i),
new Range(previousPartEditorRangeEndLine, previousPartEditorRangeEndCol, lineNumber, column),
message.slice(previousPartEnd, i)));
}
parts.push(newPart);
}
if (char === '\n') {
lineNumber++;
column = 1;
} else {
column++;
}
}
const lastPart = parts.at(-1);
const lastPartEnd = lastPart?.range.endExclusive ?? 0;
parts.push(new ChatRequestTextPart(
new OffsetRange(lastPartEnd, message.length),
new Range(lastPart?.editorRange.endLineNumber ?? 1, lastPart?.editorRange.endColumn ?? 1, lineNumber, column),
message.slice(lastPartEnd, message.length)));
return parts;
}
private tryToParseVariableOrAgent(message: string, offset: number, position: IPosition, parts: ReadonlyArray<IParsedChatRequestPart>): ChatRequestAgentPart | ChatRequestVariablePart | undefined {
const nextVariableMatch = message.match(variableOrAgentReg);
if (!nextVariableMatch) {
return;
}
const [full, name] = nextVariableMatch;
const variableArg = nextVariableMatch[2] ?? '';
const varRange = new OffsetRange(offset, offset + full.length);
const varEditorRange = new Range(position.lineNumber, position.column, position.lineNumber, position.column + full.length);
let agent: IChatAgentData | undefined;
if ((agent = this.agentService.getAgent(name)) && !variableArg) {
if (parts.some(p => p instanceof ChatRequestAgentPart)) {
// Only one agent allowed
return;
} else {
return new ChatRequestAgentPart(varRange, varEditorRange, agent);
}
} else if (this.variableService.hasVariable(name)) {
return new ChatRequestVariablePart(varRange, varEditorRange, name, variableArg);
}
return;
}
private async tryToParseSlashCommand(sessionId: string, message: string, offset: number, position: IPosition, parts: ReadonlyArray<IParsedChatRequestPart>): Promise<ChatRequestSlashCommandPart | ChatRequestAgentSubcommandPart | undefined> {
const nextSlashMatch = message.match(slashReg);
if (!nextSlashMatch) {
return;
}
if (parts.some(p => p instanceof ChatRequestSlashCommandPart)) {
// Only one slash command allowed
return;
}
const [full, command] = nextSlashMatch;
const slashRange = new OffsetRange(offset, offset + full.length);
const slashEditorRange = new Range(position.lineNumber, position.column, position.lineNumber, position.column + full.length);
const usedAgent = parts.find((p): p is ChatRequestAgentPart => p instanceof ChatRequestAgentPart);
if (usedAgent) {
const subCommand = usedAgent.agent.metadata.subCommands.find(c => c.name === command);
if (subCommand) {
// Valid agent subcommand
return new ChatRequestAgentSubcommandPart(slashRange, slashEditorRange, subCommand);
}
} else {
const slashCommands = await this.chatService.getSlashCommands(sessionId, CancellationToken.None);
const slashCommand = slashCommands.find(c => c.command === command);
if (slashCommand) {
// Valid standalone slash command
return new ChatRequestSlashCommandPart(slashRange, slashEditorRange, slashCommand);
}
}
return;
}
}
export interface IParsedChatRequestPart {
readonly range: OffsetRange;
readonly editorRange: IRange;
}
export class ChatRequestTextPart implements IParsedChatRequestPart {
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly text: string) { }
}
/**
* An invocation of a static variable that can be resolved by the variable service
*/
export class ChatRequestVariablePart implements IParsedChatRequestPart {
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly variableName: string, readonly variableArg: string) { }
}
/**
* An invocation of an agent that can be resolved by the agent service
*/
export class ChatRequestAgentPart implements IParsedChatRequestPart {
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly agent: IChatAgentData) { }
}
/**
* An invocation of an agent's subcommand
*/
export class ChatRequestAgentSubcommandPart implements IParsedChatRequestPart {
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly command: IChatAgentCommand) { }
}
/**
* An invocation of a standalone slash command
*/
export class ChatRequestSlashCommandPart implements IParsedChatRequestPart {
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly slashCommand: ISlashCommand) { }
}

View file

@ -224,7 +224,7 @@ export interface IChatService {
sendRequest(sessionId: string, message: string | IChatReplyFollowup, usedSlashCommand?: ISlashCommand): Promise<{ responseCompletePromise: Promise<void> } | undefined>;
removeRequest(sessionid: string, requestId: string): Promise<void>;
cancelCurrentRequestForSession(sessionId: string): void;
getSlashCommands(sessionId: string, token: CancellationToken): Promise<ISlashCommand[] | undefined>;
getSlashCommands(sessionId: string, token: CancellationToken): Promise<ISlashCommand[]>;
clearSession(sessionId: string): void;
addRequest(context: any): void;
addCompleteRequest(sessionId: string, message: string, response: IChatCompleteResponse): void;

View file

@ -633,7 +633,7 @@ export class ChatService extends Disposable implements IChatService {
return agents.find(a => prompt.match(new RegExp(`@${a.id}($|\\s)`)));
}
async getSlashCommands(sessionId: string, token: CancellationToken): Promise<ISlashCommand[] | undefined> {
async getSlashCommands(sessionId: string, token: CancellationToken): Promise<ISlashCommand[]> {
const model = this._sessionModels.get(sessionId);
if (!model) {
throw new Error(`Unknown session: ${sessionId}`);

View file

@ -33,6 +33,7 @@ export const IChatVariablesService = createDecorator<IChatVariablesService>('ICh
export interface IChatVariablesService {
_serviceBrand: undefined;
registerVariable(data: IChatVariableData, resolver: IChatVariableResolver): IDisposable;
hasVariable(name: string): boolean;
getVariables(): Iterable<Readonly<IChatVariableData>>;
/**
@ -102,6 +103,10 @@ export class ChatVariablesService implements IChatVariablesService {
};
}
hasVariable(name: string): boolean {
return this._resolver.has(name.toLowerCase());
}
getVariables(): Iterable<Readonly<IChatVariableData>> {
const all = Iterable.map(this._resolver.values(), data => data.data);
return Iterable.filter(all, data => !data.hidden);

View file

@ -0,0 +1,60 @@
[
{
range: {
start: 0,
endExclusive: 6
},
editorRange: {
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: 7
},
agent: {
id: "agent",
metadata: {
description: "",
subCommands: [ { name: "subCommand" } ]
}
}
},
{
range: {
start: 6,
endExclusive: 18
},
editorRange: {
startLineNumber: 1,
startColumn: 7,
endLineNumber: 2,
endColumn: 4
},
text: " Please \ndo "
},
{
range: {
start: 18,
endExclusive: 29
},
editorRange: {
startLineNumber: 2,
startColumn: 4,
endLineNumber: 2,
endColumn: 15
},
command: { name: "subCommand" }
},
{
range: {
start: 29,
endExclusive: 63
},
editorRange: {
startLineNumber: 2,
startColumn: 15,
endLineNumber: 3,
endColumn: 18
},
text: " with @selection\nand @debugConsole"
}
]

View file

@ -0,0 +1,15 @@
[
{
range: {
start: 0,
endExclusive: 21
},
editorRange: {
startLineNumber: 1,
startColumn: 1,
endLineNumber: 3,
endColumn: 7
},
text: "line 1\nline 2\r\nline 3"
}
]

View file

@ -0,0 +1,73 @@
[
{
range: {
start: 0,
endExclusive: 10
},
editorRange: {
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: 11
},
text: "Hello Mr. "
},
{
range: {
start: 10,
endExclusive: 16
},
editorRange: {
startLineNumber: 1,
startColumn: 11,
endLineNumber: 1,
endColumn: 17
},
agent: {
id: "agent",
metadata: {
description: "",
subCommands: [ { name: "subCommand" } ]
}
}
},
{
range: {
start: 16,
endExclusive: 17
},
editorRange: {
startLineNumber: 1,
startColumn: 17,
endLineNumber: 1,
endColumn: 18
},
text: " "
},
{
range: {
start: 17,
endExclusive: 28
},
editorRange: {
startLineNumber: 1,
startColumn: 18,
endLineNumber: 1,
endColumn: 29
},
command: { name: "subCommand" }
},
{
range: {
start: 28,
endExclusive: 35
},
editorRange: {
startLineNumber: 1,
startColumn: 29,
endLineNumber: 1,
endColumn: 36
},
text: " thanks"
}
]

View file

@ -0,0 +1,60 @@
[
{
range: {
start: 0,
endExclusive: 6
},
editorRange: {
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: 7
},
agent: {
id: "agent",
metadata: {
description: "",
subCommands: [ { name: "subCommand" } ]
}
}
},
{
range: {
start: 6,
endExclusive: 17
},
editorRange: {
startLineNumber: 1,
startColumn: 7,
endLineNumber: 1,
endColumn: 18
},
text: " Please do "
},
{
range: {
start: 17,
endExclusive: 28
},
editorRange: {
startLineNumber: 1,
startColumn: 18,
endLineNumber: 1,
endColumn: 29
},
command: { name: "subCommand" }
},
{
range: {
start: 28,
endExclusive: 35
},
editorRange: {
startLineNumber: 1,
startColumn: 29,
endLineNumber: 1,
endColumn: 36
},
text: " thanks"
}
]

View file

@ -0,0 +1,15 @@
[
{
range: {
start: 0,
endExclusive: 13
},
editorRange: {
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: 14
},
text: "/explain this"
}
]

View file

@ -0,0 +1,15 @@
[
{
range: {
start: 0,
endExclusive: 26
},
editorRange: {
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: 27
},
text: "What does @selection mean?"
}
]

View file

@ -0,0 +1,28 @@
[
{
range: {
start: 0,
endExclusive: 4
},
editorRange: {
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: 5
},
slashCommand: { command: "fix" }
},
{
range: {
start: 4,
endExclusive: 9
},
editorRange: {
startLineNumber: 1,
startColumn: 5,
endLineNumber: 1,
endColumn: 10
},
text: " /fix"
}
]

View file

@ -0,0 +1,15 @@
[
{
range: {
start: 0,
endExclusive: 4
},
editorRange: {
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: 5
},
text: "test"
}
]

View file

@ -0,0 +1,28 @@
[
{
range: {
start: 0,
endExclusive: 4
},
editorRange: {
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: 5
},
slashCommand: { command: "fix" }
},
{
range: {
start: 4,
endExclusive: 9
},
editorRange: {
startLineNumber: 1,
startColumn: 5,
endLineNumber: 1,
endColumn: 10
},
text: " this"
}
]

View file

@ -0,0 +1,42 @@
[
{
range: {
start: 0,
endExclusive: 10
},
editorRange: {
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: 11
},
text: "What does "
},
{
range: {
start: 10,
endExclusive: 20
},
editorRange: {
startLineNumber: 1,
startColumn: 11,
endLineNumber: 1,
endColumn: 21
},
variableName: "selection",
variableArg: ""
},
{
range: {
start: 20,
endExclusive: 26
},
editorRange: {
startLineNumber: 1,
startColumn: 21,
endLineNumber: 1,
endColumn: 27
},
text: " mean?"
}
]

View file

@ -0,0 +1,135 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { mockObject } from 'vs/base/test/common/mock';
import { assertSnapshot } from 'vs/base/test/common/snapshot';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { ILogService, NullLogService } from 'vs/platform/log/common/log';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ChatAgentService, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser';
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
suite('ChatRequestParser', () => {
const testDisposables = ensureNoDisposablesAreLeakedInTestSuite();
let instantiationService: TestInstantiationService;
let parser: ChatRequestParser;
setup(async () => {
instantiationService = testDisposables.add(new TestInstantiationService());
instantiationService.stub(IStorageService, testDisposables.add(new TestStorageService()));
instantiationService.stub(ILogService, new NullLogService());
instantiationService.stub(IExtensionService, new TestExtensionService());
instantiationService.stub(IChatAgentService, testDisposables.add(instantiationService.createInstance(ChatAgentService)));
});
test('plain text', async () => {
parser = instantiationService.createInstance(ChatRequestParser);
const result = await parser.parseChatRequest('1', 'test');
await assertSnapshot(result);
});
test('_plain text with newlines', async () => {
parser = instantiationService.createInstance(ChatRequestParser);
const text = 'line 1\nline 2\r\nline 3';
const result = await parser.parseChatRequest('1', text);
await assertSnapshot(result);
});
test('slash command', async () => {
const chatService = mockObject<IChatService>()({});
chatService.getSlashCommands.returns(Promise.resolve([{ command: 'fix' }]));
instantiationService.stub(IChatService, chatService as any);
parser = instantiationService.createInstance(ChatRequestParser);
const text = '/fix this';
const result = await parser.parseChatRequest('1', text);
await assertSnapshot(result);
});
test('invalid slash command', async () => {
const chatService = mockObject<IChatService>()({});
chatService.getSlashCommands.returns(Promise.resolve([{ command: 'fix' }]));
instantiationService.stub(IChatService, chatService as any);
parser = instantiationService.createInstance(ChatRequestParser);
const text = '/explain this';
const result = await parser.parseChatRequest('1', text);
await assertSnapshot(result);
});
test('multiple slash commands', async () => {
const chatService = mockObject<IChatService>()({});
chatService.getSlashCommands.returns(Promise.resolve([{ command: 'fix' }]));
instantiationService.stub(IChatService, chatService as any);
parser = instantiationService.createInstance(ChatRequestParser);
const text = '/fix /fix';
const result = await parser.parseChatRequest('1', text);
await assertSnapshot(result);
});
test('variables', async () => {
const variablesService = mockObject<IChatVariablesService>()({});
variablesService.hasVariable.returns(true);
instantiationService.stub(IChatVariablesService, variablesService as any);
parser = instantiationService.createInstance(ChatRequestParser);
const text = 'What does @selection mean?';
const result = await parser.parseChatRequest('1', text);
await assertSnapshot(result);
});
test('invalid variables', async () => {
const variablesService = mockObject<IChatVariablesService>()({});
variablesService.hasVariable.returns(false);
instantiationService.stub(IChatVariablesService, variablesService as any);
parser = instantiationService.createInstance(ChatRequestParser);
const text = 'What does @selection mean?';
const result = await parser.parseChatRequest('1', text);
await assertSnapshot(result);
});
test('agents', async () => {
const agentsService = mockObject<IChatAgentService>()({});
agentsService.getAgent.returns(<IChatAgentData>{ id: 'agent', metadata: { description: '', subCommands: [{ name: 'subCommand' }] } });
instantiationService.stub(IChatAgentService, agentsService as any);
parser = instantiationService.createInstance(ChatRequestParser);
const result = await parser.parseChatRequest('1', '@agent Please do /subCommand thanks');
await assertSnapshot(result);
});
test('agent not first', async () => {
const agentsService = mockObject<IChatAgentService>()({});
agentsService.getAgent.returns(<IChatAgentData>{ id: 'agent', metadata: { description: '', subCommands: [{ name: 'subCommand' }] } });
instantiationService.stub(IChatAgentService, agentsService as any);
parser = instantiationService.createInstance(ChatRequestParser);
const result = await parser.parseChatRequest('1', 'Hello Mr. @agent /subCommand thanks');
await assertSnapshot(result);
});
test('_agents and variables and multiline', async () => {
const agentsService = mockObject<IChatAgentService>()({});
agentsService.getAgent.returns(<IChatAgentData>{ id: 'agent', metadata: { description: '', subCommands: [{ name: 'subCommand' }] } });
instantiationService.stub(IChatAgentService, agentsService as any);
const variablesService = mockObject<IChatVariablesService>()({});
variablesService.hasVariable.returns(true);
instantiationService.stub(IChatVariablesService, variablesService as any);
parser = instantiationService.createInstance(ChatRequestParser);
const result = await parser.parseChatRequest('1', '@agent Please \ndo /subCommand with @selection\nand @debugConsole');
await assertSnapshot(result);
});
});

View file

@ -16,7 +16,7 @@ import { ILanguageService } from 'vs/editor/common/languages/language';
import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ICommentService } from 'vs/workbench/contrib/comments/browser/commentService';
import { SimpleCommentEditor } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor';
import { STARTING_EDITOR_HEIGHT, SimpleCommentEditor, calculateEditorHeight } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor';
import { Selection } from 'vs/editor/common/core/selection';
import { Emitter, Event } from 'vs/base/common/event';
import { INotificationService } from 'vs/platform/notification/common/notification';
@ -72,6 +72,8 @@ export class CommentNode<T extends IRange | ICellRange> extends Disposable {
private _commentEditor: SimpleCommentEditor | null = null;
private _commentEditorDisposables: IDisposable[] = [];
private _commentEditorModel: ITextModel | null = null;
private _editorHeight = STARTING_EDITOR_HEIGHT;
private _isPendingLabel!: HTMLElement;
private _timestamp: HTMLElement | undefined;
private _timestampWidget: TimestampWidget | undefined;
@ -367,7 +369,8 @@ export class CommentNode<T extends IRange | ICellRange> extends Disposable {
async submitComment(): Promise<void> {
if (this._commentEditor && this._commentFormActions) {
this._commentFormActions.triggerDefaultAction();
await this._commentFormActions.triggerDefaultAction();
this.pendingEdit = undefined;
}
}
@ -479,11 +482,11 @@ export class CommentNode<T extends IRange | ICellRange> extends Disposable {
this._commentEditor.setModel(this._commentEditorModel);
this._commentEditor.setValue(this.pendingEdit ?? this.commentBodyValue);
this.pendingEdit = undefined;
this._commentEditor.layout({ width: container.clientWidth - 14, height: 90 });
this._commentEditor.layout({ width: container.clientWidth - 14, height: this._editorHeight });
this._commentEditor.focus();
dom.scheduleAtNextAnimationFrame(() => {
this._commentEditor!.layout({ width: container.clientWidth - 14, height: 90 });
this._commentEditor!.layout({ width: container.clientWidth - 14, height: this._editorHeight });
this._commentEditor!.focus();
});
@ -518,10 +521,30 @@ export class CommentNode<T extends IRange | ICellRange> extends Disposable {
}
}));
this.calculateEditorHeight();
this._register((this._commentEditorModel.onDidChangeContent(() => {
if (this._commentEditor && this.calculateEditorHeight()) {
this._commentEditor.layout({ height: this._editorHeight, width: this._commentEditor.getLayoutInfo().width });
this._commentEditor.render(true);
}
})));
this._register(this._commentEditor);
this._register(this._commentEditorModel);
}
private calculateEditorHeight(): boolean {
if (this._commentEditor) {
const newEditorHeight = calculateEditorHeight(this._commentEditor, this._editorHeight);
if (newEditorHeight !== this._editorHeight) {
this._editorHeight = newEditorHeight;
return true;
}
}
return false;
}
getPendingEdit(): string | undefined {
const model = this._commentEditor?.getModel();
if (model && model.getValueLength() > 0) {
@ -550,7 +573,7 @@ export class CommentNode<T extends IRange | ICellRange> extends Disposable {
}
layout() {
this._commentEditor?.layout();
this._commentEditor?.layout({ width: this._commentEditor.getLayoutInfo().width, height: this._editorHeight });
const scrollWidth = this._body.scrollWidth;
const width = dom.getContentWidth(this._body);
const scrollHeight = this._body.scrollHeight;

View file

@ -28,7 +28,7 @@ import { ICommentService } from 'vs/workbench/contrib/comments/browser/commentSe
import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys';
import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import { SimpleCommentEditor } from './simpleCommentEditor';
import { STARTING_EDITOR_HEIGHT, SimpleCommentEditor, calculateEditorHeight } from './simpleCommentEditor';
const COMMENT_SCHEME = 'comment';
let INMEM_MODEL_ID = 0;
@ -45,6 +45,7 @@ export class CommentReply<T extends IRange | ICellRange> extends Disposable {
private _commentFormActions!: CommentFormActions;
private _commentEditorActions!: CommentFormActions;
private _reviewThreadReplyButton!: HTMLElement;
private _editorHeight = STARTING_EDITOR_HEIGHT;
constructor(
readonly owner: string,
@ -86,10 +87,15 @@ export class CommentReply<T extends IRange | ICellRange> extends Disposable {
const model = this.modelService.createModel(this._pendingComment || '', this.languageService.createByFilepathOrFirstLine(resource), resource, false);
this._register(model);
this.commentEditor.setModel(model);
this.calculateEditorHeight();
this._register((this.commentEditor.getModel()!.onDidChangeContent(() => {
this._register((model.onDidChangeContent(() => {
this.setCommentEditorDecorations();
this.commentEditorIsEmpty?.set(!this.commentEditor.getValue());
if (this.calculateEditorHeight()) {
this.commentEditor.layout({ height: this._editorHeight, width: this.commentEditor.getLayoutInfo().width });
this.commentEditor.render(true);
}
})));
this.createTextModelListener(this.commentEditor, this.form);
@ -110,6 +116,15 @@ export class CommentReply<T extends IRange | ICellRange> extends Disposable {
this.createCommentWidgetEditorActions(this._editorActions, model);
}
private calculateEditorHeight(): boolean {
const newEditorHeight = calculateEditorHeight(this.commentEditor, this._editorHeight);
if (newEditorHeight !== this._editorHeight) {
this._editorHeight = newEditorHeight;
return true;
}
return false;
}
public updateCommentThread(commentThread: languages.CommentThread<IRange | ICellRange>) {
const isReplying = this.commentEditor.hasTextFocus();
@ -137,7 +152,7 @@ export class CommentReply<T extends IRange | ICellRange> extends Disposable {
}
public layout(widthInPixel: number) {
this.commentEditor.layout({ height: 5 * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ });
this.commentEditor.layout({ height: this._editorHeight, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ });
}
public focusIfNeeded() {
@ -174,7 +189,8 @@ export class CommentReply<T extends IRange | ICellRange> extends Disposable {
}
async submitComment(): Promise<void> {
return this._commentFormActions?.triggerDefaultAction();
await this._commentFormActions?.triggerDefaultAction();
this._pendingComment = undefined;
}
setCommentEditorDecorations() {

View file

@ -382,7 +382,6 @@
}
.review-widget .body .edit-textarea {
height: 90px;
margin: 5px 0 10px 0;
margin-right: 12px;
}

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { EditorOption, IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { EditorAction, EditorContributionInstantiation, EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
@ -25,9 +25,11 @@ import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/comment
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
export const ctxCommentEditorFocused = new RawContextKey<boolean>('commentEditorFocused', false);
export const STARTING_EDITOR_HEIGHT = 5 * 18;
export const MAX_EDITOR_HEIGHT = 25 * 18;
export class SimpleCommentEditor extends CodeEditorWidget {
private _parentThread: ICommentThreadWidget;
@ -109,3 +111,16 @@ export class SimpleCommentEditor extends CodeEditorWidget {
};
}
}
export function calculateEditorHeight(editor: ICodeEditor, currentHeight: number): number {
const layoutInfo = editor.getLayoutInfo();
const contentHeight = editor.getContentHeight();
const lineHeight = editor.getOption(EditorOption.lineHeight);
if ((contentHeight > layoutInfo.height) ||
(contentHeight < layoutInfo.height && currentHeight > STARTING_EDITOR_HEIGHT)) {
const linesToAdd = Math.ceil((contentHeight - layoutInfo.height) / lineHeight);
const newEditorHeight = Math.min(MAX_EDITOR_HEIGHT, layoutInfo.height + (lineHeight * linesToAdd));
return newEditorHeight;
}
return currentHeight;
}

View file

@ -454,10 +454,10 @@ configurationRegistry.registerConfiguration({
enum: ['floating', 'docked', 'commandCenter', 'hidden'],
markdownDescription: nls.localize({ comment: ['This is the description for a setting'], key: 'toolBarLocation' }, "Controls the location of the debug toolbar. Either `floating` in all views, `docked` in the debug view, `commandCenter` (requires `{0}`), or `hidden`.", '#window.commandCenter#', '#window.titleBarStyle#'),
default: 'floating',
enumDescriptions: [
markdownEnumDescriptions: [
nls.localize('debugToolBar.floating', "Show debug toolbar in all views."),
nls.localize('debugToolBar.docked', "Show debug toolbar only in debug views."),
nls.localize('debugToolBar.commandCenter', "Show debug toolbar in the command center."),
nls.localize('debugToolBar.commandCenter', "`(Experimental)` Show debug toolbar in the command center."),
nls.localize('debugToolBar.hidden', "Do not show debug toolbar."),
]
},

View file

@ -59,7 +59,7 @@ export class EmptyView extends ViewPane {
onDrop: e => {
container.style.backgroundColor = '';
const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: !isWeb || isTemporaryWorkspace(this.contextService.getWorkspace()) });
dropHandler.handleDrop(e, () => undefined, () => undefined);
dropHandler.handleDrop(e);
},
onDragEnter: () => {
const color = this.themeService.getColorTheme().getColor(listDropBackground);

View file

@ -708,7 +708,7 @@ class OpenEditorsDragAndDrop implements IListDragAndDrop<OpenEditor | IEditorGro
});
this.editorGroupService.activateGroup(group);
} else {
this.dropHandler.handleDrop(originalEvent, () => group, () => group.focus(), index);
this.dropHandler.handleDrop(originalEvent, () => group, () => group.focus(), { index });
}
}
}

View file

@ -483,9 +483,10 @@ export class ApplyPreviewEdits extends AbstractInlineChatAction {
constructor() {
super({
id: ACTION_ACCEPT_CHANGES,
title: localize('apply1', 'Accept Changes'),
title: { value: localize('apply1', 'Accept Changes'), original: 'Accept Changes' },
shortTitle: localize('apply2', 'Accept'),
icon: Codicon.check,
f1: true,
precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE, ContextKeyExpr.or(CTX_INLINE_CHAT_DOCUMENT_CHANGED.toNegated(), CTX_INLINE_CHAT_EDIT_MODE.notEqualsTo(EditMode.Preview))),
keybinding: [{
weight: KeybindingWeight.EditorContrib + 10,
@ -493,7 +494,7 @@ export class ApplyPreviewEdits extends AbstractInlineChatAction {
}, {
primary: KeyCode.Escape,
weight: KeybindingWeight.EditorContrib,
when: CTX_INLINE_CHAT_USER_DID_EDIT,
when: CTX_INLINE_CHAT_USER_DID_EDIT
}],
menu: {
when: CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChateResponseTypes.OnlyMessages),

View file

@ -65,9 +65,11 @@ export class ConfigureDisplayLanguageAction extends Action2 {
});
disposables.add(qp.onDidAccept(async () => {
const selectedLanguage = qp.activeItems[0];
qp.hide();
await localeService.setLocale(selectedLanguage);
const selectedLanguage = qp.activeItems[0] as ILanguagePackItem | undefined;
if (selectedLanguage) {
qp.hide();
await localeService.setLocale(selectedLanguage);
}
}));
disposables.add(qp.onDidTriggerItemButton(async e => {

View file

@ -386,10 +386,26 @@ export abstract class BaseCellViewModel extends Disposable {
private _removeCellDecoration(decorationId: string) {
const options = this._resolvedCellDecorations.get(decorationId);
this._resolvedCellDecorations.delete(decorationId);
if (options) {
for (const existingOptions of this._resolvedCellDecorations.values()) {
// don't remove decorations that are applied from other entries
if (options.className === existingOptions.className) {
options.className = undefined;
}
if (options.outputClassName === existingOptions.outputClassName) {
options.outputClassName = undefined;
}
if (options.gutterClassName === existingOptions.gutterClassName) {
options.gutterClassName = undefined;
}
if (options.topClassName === existingOptions.topClassName) {
options.topClassName = undefined;
}
}
this._cellDecorationsChanged.fire({ added: [], removed: [options] });
this._resolvedCellDecorations.delete(decorationId);
}
}

View file

@ -0,0 +1,67 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
import { Event } from 'vs/base/common/event';
suite('CellDecorations', () => {
ensureNoDisposablesAreLeakedInTestSuite();
test('Add and remove a cell decoration', async function () {
await withTestNotebook(
[
['# header a', 'markdown', CellKind.Markup, [], {}],
],
async (editor, viewModel) => {
const cell = viewModel.cellAt(0);
assert.ok(cell);
let added = false;
Event.once(cell.onCellDecorationsChanged)(e => added = !!e.added.find(decoration => decoration.className === 'style1'));
const decorationIds = cell.deltaCellDecorations([], [{ className: 'style1' }]);
assert.ok(cell.getCellDecorations().find(dec => dec.className === 'style1'));
let removed = false;
Event.once(cell.onCellDecorationsChanged)(e => removed = !!e.removed.find(decoration => decoration.className === 'style1'));
cell.deltaCellDecorations(decorationIds, []);
assert.ok(!cell.getCellDecorations().find(dec => dec.className === 'style1'));
assert.ok(added);
assert.ok(removed);
});
});
test('Removing one cell decoration should not remove all', async function () {
await withTestNotebook(
[
['# header a', 'markdown', CellKind.Markup, [], {}],
],
async (editor, viewModel) => {
const cell = viewModel.cellAt(0);
assert.ok(cell);
const decorationIds = cell.deltaCellDecorations([], [{ className: 'style1', outputClassName: 'style1' }]);
cell.deltaCellDecorations([], [{ className: 'style1' }]);
let styleRemoved = false;
let outputStyleRemoved = false;
Event.once(cell.onCellDecorationsChanged)(e => {
styleRemoved = !!e.removed.find(decoration => decoration.className === 'style1');
outputStyleRemoved = !!e.removed.find(decoration => decoration.outputClassName === 'style1');
});
// remove the first style added, which should only remove the output class
cell.deltaCellDecorations(decorationIds, []);
assert.ok(!cell.getCellDecorations().find(dec => dec.outputClassName === 'style1'));
assert.ok(cell.getCellDecorations().find(dec => dec.className === 'style1'));
assert.ok(!styleRemoved);
assert.ok(outputStyleRemoved);
});
});
});

View file

@ -282,7 +282,7 @@ export abstract class AbstractListSettingWidget<TDataItem extends object> extend
this.addTooltipsToRow(rowElementGroup, item);
if (item.selected && listFocused) {
this.listDisposables.add(disposableTimeout(() => rowElement.focus()));
disposableTimeout(() => rowElement.focus(), undefined, this.listDisposables);
}
this.listDisposables.add(DOM.addDisposableListener(rowElement, 'click', (e) => {

View file

@ -2078,7 +2078,7 @@ class SCMInputWidget {
this.disposables.add(this.inputEditor.onDidChangeCursorPosition(({ position }) => {
const viewModel = this.inputEditor._getViewModel()!;
const lastLineNumber = viewModel.getLineCount();
const lastLineCol = viewModel.getLineContent(lastLineNumber).length + 1;
const lastLineCol = viewModel.getLineLength(lastLineNumber) + 1;
const viewPosition = viewModel.coordinatesConverter.convertModelPositionToViewPosition(position);
firstLineKey.set(viewPosition.lineNumber === 1 && viewPosition.column === 1);
lastLineKey.set(viewPosition.lineNumber === lastLineNumber && viewPosition.column === lastLineCol);

View file

@ -10,7 +10,7 @@ import * as dom from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Orientation } from 'vs/base/browser/ui/sash/sash';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { AutoOpenBarrier, Promises, timeout } from 'vs/base/common/async';
import { AutoOpenBarrier, Promises, disposableTimeout, timeout } from 'vs/base/common/async';
import { Codicon, getAllCodicons } from 'vs/base/common/codicons';
import { debounce } from 'vs/base/common/decorators';
import { ErrorNoTelemetry, onUnexpectedError } from 'vs/base/common/errors';
@ -759,7 +759,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._lineDataEventAddon = lineDataEventAddon;
// Delay the creation of the bell listener to avoid showing the bell when the terminal
// starts up or reconnects
setTimeout(() => {
disposableTimeout(() => {
this._register(xterm.raw.onBell(() => {
if (this._configHelper.config.enableBell) {
this.statusList.add({
@ -771,7 +771,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._audioCueService.playSound(AudioCue.terminalBell.sound.getSound());
}
}));
}, 1000);
}, 1000, this._store);
this._register(xterm.raw.onSelectionChange(async () => this._onSelectionChange()));
this._register(xterm.raw.buffer.onBufferChange(() => this._refreshAltBufferContextKey()));

View file

@ -70,7 +70,9 @@ export class TerminalStatusList extends Disposable implements ITerminalStatusLis
let result: ITerminalStatus | undefined;
for (const s of this._statuses.values()) {
if (!result || s.severity >= result.severity) {
result = s;
if (s.icon || !result?.icon) {
result = s;
}
}
}
return result;

View file

@ -81,6 +81,7 @@ export class TerminalTabList extends WorkbenchList<ITerminalInstance> {
@ILifecycleService lifecycleService: ILifecycleService,
@IHoverService private readonly _hoverService: IHoverService,
) {
const dnd = instantiationService.createInstance(TerminalTabsDragAndDrop);
super('TerminalTabsList', container,
{
getHeight: () => TerminalTabsListSizes.TabHeight,
@ -98,7 +99,7 @@ export class TerminalTabList extends WorkbenchList<ITerminalInstance> {
smoothScrolling: _configurationService.getValue<boolean>('workbench.list.smoothScrolling'),
multipleSelectionSupport: true,
paddingBottom: TerminalTabsListSizes.TabHeight,
dnd: instantiationService.createInstance(TerminalTabsDragAndDrop),
dnd,
openOnSingleClick: true
},
contextKeyService,
@ -106,6 +107,7 @@ export class TerminalTabList extends WorkbenchList<ITerminalInstance> {
_configurationService,
instantiationService,
);
this.disposables.add(dnd);
const instanceDisposables: IDisposable[] = [
this._terminalGroupService.onDidChangeInstances(() => this.refresh()),
@ -559,14 +561,16 @@ class TerminalTabsAccessibilityProvider implements IListAccessibilityProvider<IT
}
}
class TerminalTabsDragAndDrop implements IListDragAndDrop<ITerminalInstance> {
class TerminalTabsDragAndDrop extends Disposable implements IListDragAndDrop<ITerminalInstance> {
private _autoFocusInstance: ITerminalInstance | undefined;
private _autoFocusDisposable: IDisposable = Disposable.None;
private _primaryBackend: ITerminalBackend | undefined;
constructor(
@ITerminalService private readonly _terminalService: ITerminalService,
@ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService,
) {
super();
this._primaryBackend = this._terminalService.getPrimaryBackend();
}
@ -624,7 +628,7 @@ class TerminalTabsDragAndDrop implements IListDragAndDrop<ITerminalInstance> {
this._autoFocusDisposable = disposableTimeout(() => {
this._terminalService.setActiveInstance(targetInstance);
this._autoFocusInstance = undefined;
}, 500);
}, 500, this._store);
}
return {

View file

@ -92,14 +92,19 @@ suite('Workbench - TerminalStatusList', () => {
});
test('onDidChangePrimaryStatus', async () => {
const result = await new Promise<ITerminalStatus>(r => {
store.add(list.onDidRemoveStatus(r));
const result = await new Promise<ITerminalStatus | undefined>(r => {
store.add(list.onDidChangePrimaryStatus(r));
list.add({ id: 'test', severity: Severity.Info });
list.remove('test');
});
deepStrictEqual(result, { id: 'test', severity: Severity.Info });
});
test('primary is not updated to status without an icon', async () => {
list.add({ id: 'test', severity: Severity.Info, icon: Codicon.check });
list.add({ id: 'warning', severity: Severity.Warning });
deepStrictEqual(list.primary, { id: 'test', severity: Severity.Info, icon: Codicon.check });
});
test('add', () => {
statusesEqual(list, []);
list.add({ id: 'info', severity: Severity.Info });

View file

@ -1383,6 +1383,7 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon {
}
},
Math.max(500, this.stats.maxLatency * 3 / 2),
this._store
);
}

View file

@ -820,7 +820,21 @@ export class TimelinePane extends ViewPane {
this.setLoadingUriMessage();
} else {
this.updateFilename(this.labelService.getUriBasenameLabel(this.uri));
this.message = localize('timeline.noTimelineInfo', "No timeline information was provided.");
const scmProviderCount = this.contextKeyService.getContextKeyValue<number>('scm.providerCount');
if (this.timelineService.getSources().filter(({ id }) => !this.excludedSources.has(id)).length === 0) {
this.message = localize('timeline.noTimelineSourcesEnabled', "All timeline sources have been filtered out.");
} else {
if (this.configurationService.getValue('workbench.localHistory.enabled') && !this.excludedSources.has('timeline.localHistory')) {
this.message = localize('timeline.noLocalHistoryYet', "Local History will track recent changes as you save them unless the file has been excluded or is too large.");
} else if (this.excludedSources.size > 0) {
this.message = localize('timeline.noTimelineInfoFromEnabledSources', "No filtered timeline information was provided.");
} else {
this.message = localize('timeline.noTimelineInfo', "No timeline information was provided.");
}
}
if (!scmProviderCount || scmProviderCount === 0) {
this.message += ' ' + localize('timeline.noSCM', "Source Control has not been configured.");
}
}
} else {
this.updateFilename(this.labelService.getUriBasenameLabel(this.uri));

View file

@ -62,24 +62,63 @@
padding: 0 4px;
}
.profile-type-widget {
.profile-edit-widget {
padding: 4px 6px 0px 11px;
}
.profile-edit-widget > .profile-icon-container {
display: flex;
margin-bottom: 8px;
}
.profile-edit-widget > .profile-icon-container > .profile-icon {
cursor: pointer;
padding: 3px;
border-radius: 5px;
}
.profile-edit-widget > .profile-icon-container > .profile-icon.codicon{
font-size: 18px;
}
.profile-edit-widget > .profile-icon-container > .profile-icon:hover {
outline: 1px dashed var(--vscode-toolbar-hoverOutline);
outline-offset: -1px;
background-color: var(--vscode-toolbar-hoverBackground);
}
.profile-edit-widget > .profile-type-container {
display: flex;
margin: 0px 6px 8px 11px;
align-items: center;
justify-content: space-between;
font-size: 12px;
margin-bottom: 8px;
}
.profile-type-widget>.profile-type-select-container {
.profile-edit-widget > .profile-icon-container > .profile-icon-label,
.profile-edit-widget > .profile-type-container > .profile-type-create-label {
width: 90px;
display: inline-flex;
align-items: center;
}
.profile-edit-widget > .profile-icon-container > .profile-icon-id {
display: inline-flex;
align-items: center;
margin-left: 5px;
opacity: .8;
font-size: 0.9em;
}
.profile-edit-widget > .profile-type-container > .profile-type-select-container {
overflow: hidden;
padding-left: 10px;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.profile-type-widget>.profile-type-select-container>.monaco-select-box {
.profile-edit-widget > .profile-type-container > .profile-type-select-container > .monaco-select-box {
cursor: pointer;
line-height: 17px;
padding: 2px 23px 2px 8px;

View file

@ -40,7 +40,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { defaultButtonStyles, defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles';
import { defaultButtonStyles, defaultInputBoxStyles, defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles';
import { generateUuid } from 'vs/base/common/uuid';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { EditorsOrder } from 'vs/workbench/common/editor';
@ -71,10 +71,19 @@ import { MarkdownString } from 'vs/base/common/htmlContent';
import { renderMarkdown } from 'vs/base/browser/markdownRenderer';
import { showWindowLogActionId } from 'vs/workbench/services/log/common/logConstants';
import { ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
import { ThemeIcon } from 'vs/base/common/themables';
import { IHoverService } from 'vs/workbench/services/hover/browser/hover';
import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget';
import { DEFAULT_ICON, ICONS } from 'vs/workbench/services/userDataProfile/common/userDataProfileIcons';
import { WorkbenchIconSelectBox } from 'vs/workbench/browser/iconSelectBox';
import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
interface IUserDataProfileTemplate {
readonly name: string;
readonly shortName?: string;
readonly icon?: string;
readonly settings?: string;
readonly keybindings?: string;
readonly tasks?: string;
@ -89,6 +98,7 @@ function isUserDataProfileTemplate(thing: unknown): thing is IUserDataProfileTem
return !!(candidate && typeof candidate === 'object'
&& (candidate.name && typeof candidate.name === 'string')
&& (isUndefined(candidate.shortName) || typeof candidate.shortName === 'string')
&& (isUndefined(candidate.icon) || typeof candidate.icon === 'string')
&& (isUndefined(candidate.settings) || typeof candidate.settings === 'string')
&& (isUndefined(candidate.globalState) || typeof candidate.globalState === 'string')
&& (isUndefined(candidate.extensions) || typeof candidate.extensions === 'string'));
@ -132,6 +142,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IContextViewService private readonly contextViewService: IContextViewService,
@IHoverService private readonly hoverService: IHoverService,
@ILogService private readonly logService: ILogService,
) {
super();
@ -322,7 +333,14 @@ export class UserDataProfileImportExportService extends Disposable implements IU
disposables.add(quickPick.onDidChangeValue(validate));
let result: { name: string; items: ReadonlyArray<IQuickPickItem> } | undefined;
let icon = DEFAULT_ICON;
if (profile?.icon) {
icon = ThemeIcon.fromId(profile.icon);
}
if (isUserDataProfileTemplate(source) && source.icon) {
icon = ThemeIcon.fromId(source.icon);
}
let result: { name: string; items: ReadonlyArray<IQuickPickItem>; icon?: string | null } | undefined;
disposables.add(Event.any(quickPick.onDidCustom, quickPick.onDidAccept)(() => {
const name = quickPick.value.trim();
if (!name) {
@ -332,15 +350,74 @@ export class UserDataProfileImportExportService extends Disposable implements IU
if (quickPick.validationMessage) {
return;
}
result = { name, items: quickPick.selectedItems };
result = { name, items: quickPick.selectedItems, icon: icon.id === DEFAULT_ICON.id ? null : icon.id };
quickPick.hide();
quickPick.severity = Severity.Ignore;
quickPick.validationMessage = undefined;
}));
const domNode = DOM.$('.profile-edit-widget');
const profileIconContainer = DOM.$('.profile-icon-container');
DOM.append(profileIconContainer, DOM.$('.profile-icon-label', undefined, localize('icon', "Icon:")));
const profileIconElement = DOM.append(profileIconContainer, DOM.$(`.profile-icon${ThemeIcon.asCSSSelector(icon)}`));
profileIconElement.tabIndex = 0;
profileIconElement.role = 'button';
profileIconElement.ariaLabel = localize('select icon', "Icon: {0}", icon.id);
const iconSelectBox = disposables.add(this.instantiationService.createInstance(WorkbenchIconSelectBox, { icons: ICONS, inputBoxStyles: defaultInputBoxStyles }));
const dimension = new DOM.Dimension(496, 260);
iconSelectBox.layout(dimension);
let hoverWidget: IHoverWidget | undefined;
const updateIcon = (updated: ThemeIcon | undefined) => {
icon = updated ?? DEFAULT_ICON;
profileIconElement.className = `profile-icon ${ThemeIcon.asClassName(icon)}`;
};
disposables.add(iconSelectBox.onDidSelect(selectedIcon => {
if (icon.id !== selectedIcon.id) {
updateIcon(selectedIcon);
}
hoverWidget?.dispose();
profileIconElement.focus();
}));
const showIconSelectBox = () => {
iconSelectBox.clearInput();
hoverWidget = this.hoverService.showHover({
content: iconSelectBox.domNode,
target: profileIconElement,
hoverPosition: HoverPosition.BELOW,
showPointer: true,
hideOnHover: false,
}, true);
if (hoverWidget) {
iconSelectBox.layout(dimension);
disposables.add(hoverWidget);
}
iconSelectBox.focus();
};
disposables.add(DOM.addDisposableListener(profileIconElement, DOM.EventType.CLICK, (e: MouseEvent) => {
DOM.EventHelper.stop(e, true);
showIconSelectBox();
}));
disposables.add(DOM.addDisposableListener(profileIconElement, DOM.EventType.KEY_DOWN, e => {
const event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
DOM.EventHelper.stop(event, true);
showIconSelectBox();
}
}));
disposables.add(DOM.addDisposableListener(iconSelectBox.domNode, DOM.EventType.KEY_DOWN, e => {
const event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.Escape)) {
DOM.EventHelper.stop(event, true);
hoverWidget?.dispose();
profileIconElement.focus();
}
}));
if (!profile && !isUserDataProfileTemplate(source)) {
const domNode = DOM.$('.profile-type-widget');
DOM.append(domNode, DOM.$('.profile-type-create-label', undefined, localize('create from', "Copy from:")));
const profileTypeContainer = DOM.append(domNode, DOM.$('.profile-type-container'));
DOM.append(profileTypeContainer, DOM.$('.profile-type-create-label', undefined, localize('create from', "Copy from:")));
const separator = { text: '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500', isDisabled: true };
const profileOptions: (ISelectOptionItem & { id?: string; source?: IUserDataProfile | URI })[] = [];
profileOptions.push({ text: localize('empty profile', "None") });
@ -369,9 +446,17 @@ export class UserDataProfileImportExportService extends Disposable implements IU
};
const initialIndex = findOptionIndex();
const selectBox = disposables.add(this.instantiationService.createInstance(SelectBox, profileOptions, initialIndex, this.contextViewService, defaultSelectBoxStyles, { useCustomDrawn: true }));
selectBox.render(DOM.append(domNode, DOM.$('.profile-type-select-container')));
quickPick.widget = domNode;
const selectBox = disposables.add(this.instantiationService.createInstance(SelectBox,
profileOptions,
initialIndex,
this.contextViewService,
defaultSelectBoxStyles,
{
useCustomDrawn: true,
ariaLabel: localize('copy profile from', "Copy profile from"),
}
));
selectBox.render(DOM.append(profileTypeContainer, DOM.$('.profile-type-select-container')));
if (profileOptions[initialIndex].source) {
quickPick.value = this.generateProfileName(profileOptions[initialIndex].text);
@ -382,6 +467,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU
for (const resource of resources) {
resource.picked = option.source && !(option.source instanceof URI) ? !option.source?.useDefaultFlags?.[resource.id] : true;
}
updateIcon(!(option.source instanceof URI) && option.source?.icon ? ThemeIcon.fromId(option.source.icon) : undefined);
update();
};
@ -392,6 +478,9 @@ export class UserDataProfileImportExportService extends Disposable implements IU
}));
}
DOM.append(domNode, profileIconContainer);
quickPick.widget = domNode;
quickPick.show();
await new Promise<void>((c, e) => {
@ -421,21 +510,21 @@ export class UserDataProfileImportExportService extends Disposable implements IU
extensions: !result.items.includes(extensions)
};
if (profile) {
await this.userDataProfileManagementService.updateProfile(profile, { name: result.name, useDefaultFlags: profile.useDefaultFlags && !useDefaultFlags ? {} : useDefaultFlags });
await this.userDataProfileManagementService.updateProfile(profile, { name: result.name, icon: result.icon, useDefaultFlags: profile.useDefaultFlags && !useDefaultFlags ? {} : useDefaultFlags });
} else {
if (source instanceof URI) {
this.telemetryService.publicLog2<CreateProfileInfoEvent, CreateProfileInfoClassification>('userDataProfile.createFromTemplate', createProfileTelemetryData);
await this.importProfile(source, { mode: 'apply', name: result.name, useDefaultFlags });
await this.importProfile(source, { mode: 'apply', name: result.name, useDefaultFlags, icon: result.icon ? result.icon : undefined });
} else if (isUserDataProfile(source)) {
this.telemetryService.publicLog2<CreateProfileInfoEvent, CreateProfileInfoClassification>('userDataProfile.createFromProfile', createProfileTelemetryData);
await this.createFromProfile(source, result.name, { useDefaultFlags });
await this.createFromProfile(source, result.name, { useDefaultFlags, icon: result.icon ? result.icon : undefined });
} else if (isUserDataProfileTemplate(source)) {
source.name = result.name;
this.telemetryService.publicLog2<CreateProfileInfoEvent, CreateProfileInfoClassification>('userDataProfile.createFromExternalTemplate', createProfileTelemetryData);
await this.createAndSwitch(source, false, true, { useDefaultFlags }, localize('create profile', "Create Profile"));
await this.createAndSwitch(source, false, true, { useDefaultFlags, icon: result.icon ? result.icon : undefined }, localize('create profile', "Create Profile"));
} else {
this.telemetryService.publicLog2<CreateProfileInfoEvent, CreateProfileInfoClassification>('userDataProfile.createEmptyProfile', createProfileTelemetryData);
await this.userDataProfileManagementService.createAndEnterProfile(result.name, { useDefaultFlags });
await this.userDataProfileManagementService.createAndEnterProfile(result.name, { useDefaultFlags, icon: result.icon ? result.icon : undefined });
}
}
} catch (error) {
@ -600,6 +689,10 @@ export class UserDataProfileImportExportService extends Disposable implements IU
profileTemplate.name = options.name;
}
if (options?.icon) {
profileTemplate.icon = options.icon;
}
return profileTemplate;
}
@ -1315,6 +1408,7 @@ class UserDataProfileExportState extends UserDataProfileImportExportState {
location: profile.location,
isDefault: profile.isDefault,
shortName: profile.shortName,
icon: profile.icon,
globalStorageHome: profile.globalStorageHome,
settingsResource: profile.settingsResource.with({ scheme: USER_DATA_PROFILE_EXPORT_SCHEME }),
keybindingsResource: profile.keybindingsResource.with({ scheme: USER_DATA_PROFILE_EXPORT_SCHEME }),

Some files were not shown because too many files have changed in this diff Show more