mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 21:09:43 +00:00
Merge remote-tracking branch 'origin/main' into ai-codefixes
This commit is contained in:
commit
32bb21550c
|
@ -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();
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -166,7 +166,7 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@vscode/extension-telemetry": "^0.8.4"
|
||||
"@vscode/extension-telemetry": "^0.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.x"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
16
package.json
16
package.json
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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==
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -35,4 +35,5 @@
|
|||
|
||||
.icon-select-box .icon-select-id-container .icon-select-id-label .highlight {
|
||||
color: var(--vscode-list-highlightForeground);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -342,6 +342,7 @@ export interface ISyncUserDataProfile {
|
|||
readonly collection: string;
|
||||
readonly name: string;
|
||||
readonly shortName?: string;
|
||||
readonly icon?: string;
|
||||
readonly useDefaultFlags?: UseDefaultProfileFlags;
|
||||
}
|
||||
|
||||
|
|
|
@ -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}`);
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: () => {
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
158
src/vs/workbench/common/editor/filteredEditorGroupModel.ts
Normal file
158
src/vs/workbench/common/editor/filteredEditorGroupModel.ts
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
170
src/vs/workbench/contrib/chat/common/chatRequestParser.ts
Normal file
170
src/vs/workbench/contrib/chat/common/chatRequestParser.ts
Normal 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) { }
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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}`);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
|
@ -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"
|
||||
}
|
||||
]
|
|
@ -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"
|
||||
}
|
||||
]
|
|
@ -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"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,15 @@
|
|||
[
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 13
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 14
|
||||
},
|
||||
text: "/explain this"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,15 @@
|
|||
[
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 26
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 27
|
||||
},
|
||||
text: "What does @selection mean?"
|
||||
}
|
||||
]
|
|
@ -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"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,15 @@
|
|||
[
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 4
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 5
|
||||
},
|
||||
text: "test"
|
||||
}
|
||||
]
|
|
@ -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"
|
||||
}
|
||||
]
|
|
@ -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?"
|
||||
}
|
||||
]
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -382,7 +382,6 @@
|
|||
}
|
||||
|
||||
.review-widget .body .edit-textarea {
|
||||
height: 90px;
|
||||
margin: 5px 0 10px 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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."),
|
||||
]
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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) => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()));
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 });
|
||||
|
|
|
@ -1383,6 +1383,7 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon {
|
|||
}
|
||||
},
|
||||
Math.max(500, this.stats.maxLatency * 3 / 2),
|
||||
this._store
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue